Squashed 'third_party/SPIRV-Tools/' content from commit d14db341b
git-subtree-dir: third_party/SPIRV-Tools
git-subtree-split: d14db341b834cfb3c574a258c331b3a6b1c2cbc5
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
new file mode 100644
index 0000000..6315385
--- /dev/null
+++ b/test/opt/CMakeLists.txt
@@ -0,0 +1,92 @@
+# Copyright (c) 2016 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.
+
+add_subdirectory(dominator_tree)
+add_subdirectory(loop_optimizations)
+
+add_spvtools_unittest(TARGET opt
+ SRCS aggressive_dead_code_elim_test.cpp
+ assembly_builder_test.cpp
+ block_merge_test.cpp
+ ccp_test.cpp
+ cfg_cleanup_test.cpp
+ code_sink_test.cpp
+ combine_access_chains_test.cpp
+ common_uniform_elim_test.cpp
+ compact_ids_test.cpp
+ constant_manager_test.cpp
+ copy_prop_array_test.cpp
+ dead_branch_elim_test.cpp
+ dead_insert_elim_test.cpp
+ dead_variable_elim_test.cpp
+ decoration_manager_test.cpp
+ def_use_test.cpp
+ eliminate_dead_const_test.cpp
+ eliminate_dead_functions_test.cpp
+ feature_manager_test.cpp
+ flatten_decoration_test.cpp
+ fold_spec_const_op_composite_test.cpp
+ fold_test.cpp
+ freeze_spec_const_test.cpp
+ function_test.cpp
+ if_conversion_test.cpp
+ inline_opaque_test.cpp
+ inline_test.cpp
+ insert_extract_elim_test.cpp
+ inst_bindless_check_test.cpp
+ instruction_list_test.cpp
+ instruction_test.cpp
+ ir_builder.cpp
+ ir_context_test.cpp
+ ir_loader_test.cpp
+ iterator_test.cpp
+ line_debug_info_test.cpp
+ local_access_chain_convert_test.cpp
+ local_redundancy_elimination_test.cpp
+ local_single_block_elim.cpp
+ local_single_store_elim_test.cpp
+ local_ssa_elim_test.cpp
+ module_test.cpp
+ module_utils.h
+ optimizer_test.cpp
+ pass_manager_test.cpp
+ pass_merge_return_test.cpp
+ pass_remove_duplicates_test.cpp
+ pass_utils.cpp
+ private_to_local_test.cpp
+ process_lines_test.cpp
+ propagator_test.cpp
+ reduce_load_size_test.cpp
+ redundancy_elimination_test.cpp
+ register_liveness.cpp
+ replace_invalid_opc_test.cpp
+ scalar_analysis.cpp
+ scalar_replacement_test.cpp
+ set_spec_const_default_value_test.cpp
+ simplification_test.cpp
+ strength_reduction_test.cpp
+ strip_debug_info_test.cpp
+ strip_reflect_info_test.cpp
+ struct_cfg_analysis_test.cpp
+ type_manager_test.cpp
+ types_test.cpp
+ unify_const_test.cpp
+ upgrade_memory_model_test.cpp
+ utils_test.cpp pass_utils.cpp
+ value_table_test.cpp
+ vector_dce_test.cpp
+ workaround1209_test.cpp
+ LIBS SPIRV-Tools-opt
+ PCH_FILE pch_test_opt
+)
diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp
new file mode 100644
index 0000000..e58a76f
--- /dev/null
+++ b/test/opt/aggressive_dead_code_elim_test.cpp
@@ -0,0 +1,6225 @@
+// 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 <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using AggressiveDCETest = PassTest<::testing::Test>;
+
+TEST_F(AggressiveDCETest, EliminateExtendedInst) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in vec4 Dead;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // vec4 dv = sqrt(Dead);
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs1 =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+)";
+
+ const std::string names_before =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string names_after =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string predefs2 =
+ R"(%void = OpTypeVoid
+%9 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %9
+%15 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%dv = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+OpStore %v %16
+%17 = OpLoad %v4float %Dead
+%18 = OpExtInst %v4float %1 Sqrt %17
+OpStore %dv %18
+%19 = OpLoad %v4float %v
+OpStore %gl_FragColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %9
+%15 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+OpStore %v %16
+%19 = OpLoad %v4float %v
+OpStore %gl_FragColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs1 + names_before + predefs2 + func_before,
+ predefs1 + names_after + predefs2 + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateFrexp) {
+ // Note: SPIR-V hand-edited to utilize Frexp
+ //
+ // #version 450
+ //
+ // in vec4 BaseColor;
+ // in vec4 Dead;
+ // out vec4 Color;
+ // out ivec4 iv2;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // vec4 dv = frexp(Dead, iv2);
+ // Color = v;
+ // }
+
+ const std::string predefs1 =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %iv2 %Color
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+)";
+
+ const std::string names_before =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %iv2 "iv2"
+OpName %ResType "ResType"
+OpName %Color "Color"
+)";
+
+ const std::string names_after =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %iv2 "iv2"
+OpName %Color "Color"
+)";
+
+ const std::string predefs2_before =
+ R"(%void = OpTypeVoid
+%11 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%int = OpTypeInt 32 1
+%v4int = OpTypeVector %int 4
+%_ptr_Output_v4int = OpTypePointer Output %v4int
+%iv2 = OpVariable %_ptr_Output_v4int Output
+%ResType = OpTypeStruct %v4float %v4int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%Color = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string predefs2_after =
+ R"(%void = OpTypeVoid
+%11 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%int = OpTypeInt 32 1
+%v4int = OpTypeVector %int 4
+%_ptr_Output_v4int = OpTypePointer Output %v4int
+%iv2 = OpVariable %_ptr_Output_v4int Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%Color = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %11
+%20 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%dv = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %BaseColor
+OpStore %v %21
+%22 = OpLoad %v4float %Dead
+%23 = OpExtInst %v4float %1 Frexp %22 %iv2
+OpStore %dv %23
+%24 = OpLoad %v4float %v
+OpStore %Color %24
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %11
+%20 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %BaseColor
+OpStore %v %21
+%22 = OpLoad %v4float %Dead
+%23 = OpExtInst %v4float %1 Frexp %22 %iv2
+%24 = OpLoad %v4float %v
+OpStore %Color %24
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs1 + names_before + predefs2_before + func_before,
+ predefs1 + names_after + predefs2_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateDecorate) {
+ // Note: The SPIR-V was hand-edited to add the OpDecorate
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in vec4 Dead;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // vec4 dv = Dead * 0.5;
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs1 =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+)";
+
+ const std::string names_before =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %8 RelaxedPrecision
+)";
+
+ const std::string names_after =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string predefs2_before =
+ R"(%void = OpTypeVoid
+%10 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%float_0_5 = OpConstant %float 0.5
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string predefs2_after =
+ R"(%void = OpTypeVoid
+%10 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %10
+%17 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%dv = OpVariable %_ptr_Function_v4float Function
+%18 = OpLoad %v4float %BaseColor
+OpStore %v %18
+%19 = OpLoad %v4float %Dead
+%8 = OpVectorTimesScalar %v4float %19 %float_0_5
+OpStore %dv %8
+%20 = OpLoad %v4float %v
+OpStore %gl_FragColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %10
+%17 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%18 = OpLoad %v4float %BaseColor
+OpStore %v %18
+%20 = OpLoad %v4float %v
+OpStore %gl_FragColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs1 + names_before + predefs2_before + func_before,
+ predefs1 + names_after + predefs2_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, Simple) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in vec4 Dead;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // vec4 dv = Dead;
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs1 =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+)";
+
+ const std::string names_before =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string names_after =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string predefs2 =
+ R"(%void = OpTypeVoid
+%9 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %9
+%15 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%dv = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+OpStore %v %16
+%17 = OpLoad %v4float %Dead
+OpStore %dv %17
+%18 = OpLoad %v4float %v
+OpStore %gl_FragColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %9
+%15 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+OpStore %v %16
+%18 = OpLoad %v4float %v
+OpStore %gl_FragColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs1 + names_before + predefs2 + func_before,
+ predefs1 + names_after + predefs2 + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, OptWhitelistExtension) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in vec4 Dead;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // vec4 dv = Dead;
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs1 =
+ R"(OpCapability Shader
+OpExtension "SPV_AMD_gpu_shader_int16"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+)";
+
+ const std::string names_before =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string names_after =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string predefs2 =
+ R"(%void = OpTypeVoid
+%9 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %9
+%15 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%dv = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+OpStore %v %16
+%17 = OpLoad %v4float %Dead
+OpStore %dv %17
+%18 = OpLoad %v4float %v
+OpStore %gl_FragColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %9
+%15 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+OpStore %v %16
+%18 = OpLoad %v4float %v
+OpStore %gl_FragColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs1 + names_before + predefs2 + func_before,
+ predefs1 + names_after + predefs2 + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoOptBlacklistExtension) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in vec4 Dead;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // vec4 dv = Dead;
+ // gl_FragColor = v;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+OpExtension "SPV_KHR_variable_pointers"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %9
+%15 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%dv = OpVariable %_ptr_Function_v4float Function
+%16 = OpLoad %v4float %BaseColor
+OpStore %v %16
+%17 = OpLoad %v4float %Dead
+OpStore %dv %17
+%18 = OpLoad %v4float %v
+OpStore %gl_FragColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, ElimWithCall) {
+ // This demonstrates that "dead" function calls are not eliminated.
+ // Also demonstrates that DCE will happen in presence of function call.
+ // #version 140
+ // in vec4 i1;
+ // in vec4 i2;
+ //
+ // void nothing(vec4 v)
+ // {
+ // }
+ //
+ // void main()
+ // {
+ // vec4 v1 = i1;
+ // vec4 v2 = i2;
+ // nothing(v1);
+ // gl_FragColor = vec4(0.0);
+ // }
+
+ const std::string defs_before =
+ R"( OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %i1 %i2 %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %nothing_vf4_ "nothing(vf4;"
+OpName %v "v"
+OpName %v1 "v1"
+OpName %i1 "i1"
+OpName %v2 "v2"
+OpName %i2 "i2"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%16 = OpTypeFunction %void %_ptr_Function_v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%i1 = OpVariable %_ptr_Input_v4float Input
+%i2 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%20 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %i1 %i2 %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %nothing_vf4_ "nothing(vf4;"
+OpName %v "v"
+OpName %v1 "v1"
+OpName %i1 "i1"
+OpName %i2 "i2"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%16 = OpTypeFunction %void %_ptr_Function_v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%i1 = OpVariable %_ptr_Input_v4float Input
+%i2 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%20 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %12
+%21 = OpLabel
+%v1 = OpVariable %_ptr_Function_v4float Function
+%v2 = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %i1
+OpStore %v1 %22
+%23 = OpLoad %v4float %i2
+OpStore %v2 %23
+%24 = OpLoad %v4float %v1
+OpStore %param %24
+%25 = OpFunctionCall %void %nothing_vf4_ %param
+OpStore %gl_FragColor %20
+OpReturn
+OpFunctionEnd
+%nothing_vf4_ = OpFunction %void None %16
+%v = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %12
+%21 = OpLabel
+%v1 = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %i1
+OpStore %v1 %22
+%24 = OpLoad %v4float %v1
+OpStore %param %24
+%25 = OpFunctionCall %void %nothing_vf4_ %param
+OpStore %gl_FragColor %20
+OpReturn
+OpFunctionEnd
+%nothing_vf4_ = OpFunction %void None %16
+%v = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(defs_before + func_before,
+ defs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoParamElim) {
+ // This demonstrates that unused parameters are not eliminated, but
+ // dead uses of them are.
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // vec4 foo(vec4 v1, vec4 v2)
+ // {
+ // vec4 t = -v1;
+ // return v2;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 dead;
+ // gl_FragColor = foo(dead, BaseColor);
+ // }
+
+ const std::string defs_before =
+ 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 %foo_vf4_vf4_ "foo(vf4;vf4;"
+OpName %v1 "v1"
+OpName %v2 "v2"
+OpName %t "t"
+OpName %gl_FragColor "gl_FragColor"
+OpName %dead "dead"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %param_0 "param"
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%17 = OpTypeFunction %v4float %_ptr_Function_v4float %_ptr_Function_v4float
+%_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
+%main = OpFunction %void None %13
+%20 = OpLabel
+%dead = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%param_0 = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %dead
+OpStore %param %21
+%22 = OpLoad %v4float %BaseColor
+OpStore %param_0 %22
+%23 = OpFunctionCall %v4float %foo_vf4_vf4_ %param %param_0
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string defs_after =
+ 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 %foo_vf4_vf4_ "foo(vf4;vf4;"
+OpName %v1 "v1"
+OpName %v2 "v2"
+OpName %gl_FragColor "gl_FragColor"
+OpName %dead "dead"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %param_0 "param"
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%17 = OpTypeFunction %v4float %_ptr_Function_v4float %_ptr_Function_v4float
+%_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
+%main = OpFunction %void None %13
+%20 = OpLabel
+%dead = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%param_0 = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %dead
+OpStore %param %21
+%22 = OpLoad %v4float %BaseColor
+OpStore %param_0 %22
+%23 = OpFunctionCall %v4float %foo_vf4_vf4_ %param %param_0
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_before =
+ R"(%foo_vf4_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%v2 = OpFunctionParameter %_ptr_Function_v4float
+%24 = OpLabel
+%t = OpVariable %_ptr_Function_v4float Function
+%25 = OpLoad %v4float %v1
+%26 = OpFNegate %v4float %25
+OpStore %t %26
+%27 = OpLoad %v4float %v2
+OpReturnValue %27
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%foo_vf4_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%v2 = OpFunctionParameter %_ptr_Function_v4float
+%24 = OpLabel
+%27 = OpLoad %v4float %v2
+OpReturnValue %27
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(defs_before + func_before,
+ defs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, ElimOpaque) {
+ // SPIR-V not representable from GLSL; not generatable from HLSL
+ // for the moment.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %outColor %texCoords
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpMemberName %S_t 2 "smp"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %s0 "s0"
+OpName %texCoords "texCoords"
+OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%14 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%15 = OpTypeSampledImage %14
+%S_t = OpTypeStruct %v2float %v2float %15
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%17 = OpTypeFunction %void %_ptr_Function_S_t
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%_ptr_Function_15 = OpTypePointer Function %15
+%sampler15 = OpVariable %_ptr_UniformConstant_15 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %outColor %texCoords
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %texCoords "texCoords"
+OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%14 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%15 = OpTypeSampledImage %14
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%sampler15 = OpVariable %_ptr_UniformConstant_15 UniformConstant
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %9
+%25 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%26 = OpLoad %v2float %texCoords
+%27 = OpLoad %S_t %s0
+%28 = OpCompositeInsert %S_t %26 %27 0
+%29 = OpLoad %15 %sampler15
+%30 = OpCompositeInsert %S_t %29 %28 2
+OpStore %s0 %30
+%31 = OpImageSampleImplicitLod %v4float %29 %26
+OpStore %outColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %9
+%25 = OpLabel
+%26 = OpLoad %v2float %texCoords
+%29 = OpLoad %15 %sampler15
+%31 = OpImageSampleImplicitLod %v4float %29 %26
+OpStore %outColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(defs_before + func_before,
+ defs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoParamStoreElim) {
+ // Should not eliminate stores to params
+ //
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void foo(in vec4 v1, out vec4 v2)
+ // {
+ // v2 = -v1;
+ // }
+ //
+ // void main()
+ // {
+ // foo(BaseColor, OutColor);
+ // }
+
+ const std::string assembly =
+ 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 %foo_vf4_vf4_ "foo(vf4;vf4;"
+OpName %v1 "v1"
+OpName %v2 "v2"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpName %param "param"
+OpName %param_0 "param"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%15 = OpTypeFunction %void %_ptr_Function_v4float %_ptr_Function_v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %11
+%18 = OpLabel
+%param = OpVariable %_ptr_Function_v4float Function
+%param_0 = OpVariable %_ptr_Function_v4float Function
+%19 = OpLoad %v4float %BaseColor
+OpStore %param %19
+%20 = OpFunctionCall %void %foo_vf4_vf4_ %param %param_0
+%21 = OpLoad %v4float %param_0
+OpStore %OutColor %21
+OpReturn
+OpFunctionEnd
+%foo_vf4_vf4_ = OpFunction %void None %15
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%v2 = OpFunctionParameter %_ptr_Function_v4float
+%22 = OpLabel
+%23 = OpLoad %v4float %v1
+%24 = OpFNegate %v4float %23
+OpStore %v2 %24
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, PrivateStoreElimInEntryNoCalls) {
+ // Eliminate stores to private in entry point with no calls
+ // Note: Not legal GLSL
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 1) in vec4 Dead;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // private vec4 dv;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // dv = Dead;
+ // OutColor = v;
+ // }
+
+ const std::string predefs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %Dead Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%dv = OpVariable %_ptr_Private_v4float Private
+%OutColor = 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 %Dead %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %Dead Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%9 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string main_before =
+ R"(%main = OpFunction %void None %9
+%16 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%17 = OpLoad %v4float %BaseColor
+OpStore %v %17
+%18 = OpLoad %v4float %Dead
+OpStore %dv %18
+%19 = OpLoad %v4float %v
+%20 = OpFNegate %v4float %19
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string main_after =
+ R"(%main = OpFunction %void None %9
+%16 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%17 = OpLoad %v4float %BaseColor
+OpStore %v %17
+%19 = OpLoad %v4float %v
+%20 = OpFNegate %v4float %19
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + main_before, predefs_after + main_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoPrivateStoreElimIfLoad) {
+ // Should not eliminate stores to private when there is a load
+ // Note: Not legal GLSL
+ //
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // private vec4 pv;
+ //
+ // void main()
+ // {
+ // pv = BaseColor;
+ // OutColor = pv;
+ // }
+
+ const std::string assembly =
+ 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 %pv "pv"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%pv = OpVariable %_ptr_Private_v4float Private
+%main = OpFunction %void None %7
+%13 = OpLabel
+%14 = OpLoad %v4float %BaseColor
+OpStore %pv %14
+%15 = OpLoad %v4float %pv
+%16 = OpFNegate %v4float %15
+OpStore %OutColor %16
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoPrivateStoreElimWithCall) {
+ // Should not eliminate stores to private when function contains call
+ // Note: Not legal GLSL
+ //
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // private vec4 v1;
+ //
+ // void foo()
+ // {
+ // OutColor = -v1;
+ // }
+ //
+ // void main()
+ // {
+ // v1 = BaseColor;
+ // foo();
+ // }
+
+ const std::string assembly =
+ 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 %foo_ "foo("
+OpName %OutColor "OutColor"
+OpName %v1 "v1"
+OpName %BaseColor "BaseColor"
+OpDecorate %OutColor Location 0
+OpDecorate %BaseColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%v1 = OpVariable %_ptr_Private_v4float Private
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%main = OpFunction %void None %8
+%14 = OpLabel
+%15 = OpLoad %v4float %BaseColor
+OpStore %v1 %15
+%16 = OpFunctionCall %void %foo_
+OpReturn
+OpFunctionEnd
+%foo_ = OpFunction %void None %8
+%17 = OpLabel
+%18 = OpLoad %v4float %v1
+%19 = OpFNegate %v4float %18
+OpStore %OutColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoPrivateStoreElimInNonEntry) {
+ // Should not eliminate stores to private when function is not entry point
+ // Note: Not legal GLSL
+ //
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // private vec4 v1;
+ //
+ // void foo()
+ // {
+ // v1 = BaseColor;
+ // }
+ //
+ // void main()
+ // {
+ // foo();
+ // OutColor = -v1;
+ // }
+
+ const std::string assembly =
+ 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 %foo_ "foo("
+OpName %v1 "v1"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v1 = OpVariable %_ptr_Private_v4float Private
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %8
+%14 = OpLabel
+%15 = OpFunctionCall %void %foo_
+%16 = OpLoad %v4float %v1
+%17 = OpFNegate %v4float %16
+OpStore %OutColor %17
+OpReturn
+OpFunctionEnd
+%foo_ = OpFunction %void None %8
+%18 = OpLabel
+%19 = OpLoad %v4float %BaseColor
+OpStore %v1 %19
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, WorkgroupStoreElimInEntryNoCalls) {
+ // Eliminate stores to private in entry point with no calls
+ // Note: Not legal GLSL
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 1) in vec4 Dead;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // workgroup vec4 dv;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // dv = Dead;
+ // OutColor = v;
+ // }
+
+ const std::string predefs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %dv "dv"
+OpName %Dead "Dead"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %Dead Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%dv = OpVariable %_ptr_Workgroup_v4float Workgroup
+%OutColor = 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 %Dead %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %Dead Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%9 = 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
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string main_before =
+ R"(%main = OpFunction %void None %9
+%16 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%17 = OpLoad %v4float %BaseColor
+OpStore %v %17
+%18 = OpLoad %v4float %Dead
+OpStore %dv %18
+%19 = OpLoad %v4float %v
+%20 = OpFNegate %v4float %19
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string main_after =
+ R"(%main = OpFunction %void None %9
+%16 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%17 = OpLoad %v4float %BaseColor
+OpStore %v %17
+%19 = OpLoad %v4float %v
+%20 = OpFNegate %v4float %19
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + main_before, predefs_after + main_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateDeadIfThenElse) {
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float d;
+ // if (BaseColor.x == 0)
+ // d = BaseColor.y;
+ // else
+ // d = BaseColor.z;
+ // OutColor = vec4(1.0,1.0,1.0,1.0);
+ // }
+
+ const std::string predefs_before =
+ 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 %BaseColor "BaseColor"
+OpName %d "d"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+)";
+
+ const std::string predefs_after =
+ 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 %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %7
+%22 = OpLabel
+%d = OpVariable %_ptr_Function_float Function
+%23 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%24 = OpLoad %float %23
+%25 = OpFOrdEqual %bool %24 %float_0
+OpSelectionMerge %26 None
+OpBranchConditional %25 %27 %28
+%27 = OpLabel
+%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%30 = OpLoad %float %29
+OpStore %d %30
+OpBranch %26
+%28 = OpLabel
+%31 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2
+%32 = OpLoad %float %31
+OpStore %d %32
+OpBranch %26
+%26 = OpLabel
+OpStore %OutColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %7
+%22 = OpLabel
+OpBranch %26
+%26 = OpLabel
+OpStore %OutColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateDeadIfThen) {
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float d;
+ // if (BaseColor.x == 0)
+ // d = BaseColor.y;
+ // OutColor = vec4(1.0,1.0,1.0,1.0);
+ // }
+
+ const std::string predefs_before =
+ 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 %BaseColor "BaseColor"
+OpName %d "d"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+)";
+
+ const std::string predefs_after =
+ 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 %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %7
+%21 = OpLabel
+%d = OpVariable %_ptr_Function_float Function
+%22 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%23 = OpLoad %float %22
+%24 = OpFOrdEqual %bool %23 %float_0
+OpSelectionMerge %25 None
+OpBranchConditional %24 %26 %25
+%26 = OpLabel
+%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%28 = OpLoad %float %27
+OpStore %d %28
+OpBranch %25
+%25 = OpLabel
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %7
+%21 = OpLabel
+OpBranch %25
+%25 = OpLabel
+OpStore %OutColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateDeadSwitch) {
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 1) in flat int x;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float d;
+ // switch (x) {
+ // case 0:
+ // d = BaseColor.y;
+ // }
+ // OutColor = vec4(1.0,1.0,1.0,1.0);
+ // }
+ const std::string before =
+ R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %x %BaseColor %OutColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %x "x"
+ OpName %d "d"
+ OpName %BaseColor "BaseColor"
+ OpName %OutColor "OutColor"
+ OpDecorate %x Flat
+ OpDecorate %x Location 1
+ OpDecorate %BaseColor Location 0
+ OpDecorate %OutColor Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+ %x = OpVariable %_ptr_Input_int Input
+ %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %BaseColor = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %OutColor = OpVariable %_ptr_Output_v4float Output
+ %float_1 = OpConstant %float 1
+ %27 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %d = OpVariable %_ptr_Function_float Function
+ %9 = OpLoad %int %x
+ OpSelectionMerge %11 None
+ OpSwitch %9 %11 0 %10
+ %10 = OpLabel
+ %21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+ %22 = OpLoad %float %21
+ OpStore %d %22
+ OpBranch %11
+ %11 = OpLabel
+ OpStore %OutColor %27
+ OpReturn
+ OpFunctionEnd)";
+
+ const std::string after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %x %BaseColor %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %x "x"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %x Flat
+OpDecorate %x Location 1
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%x = OpVariable %_ptr_Input_int Input
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%27 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%main = OpFunction %void None %3
+%5 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpStore %OutColor %27
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(before, after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateDeadIfThenElseNested) {
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float d;
+ // if (BaseColor.x == 0)
+ // if (BaseColor.y == 0)
+ // d = 0.0;
+ // else
+ // d = 0.25;
+ // else
+ // if (BaseColor.y == 0)
+ // d = 0.5;
+ // else
+ // d = 0.75;
+ // OutColor = vec4(1.0,1.0,1.0,1.0);
+ // }
+
+ const std::string predefs_before =
+ 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 %BaseColor "BaseColor"
+OpName %d "d"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%uint_1 = OpConstant %uint 1
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0_25 = OpConstant %float 0.25
+%float_0_5 = OpConstant %float 0.5
+%float_0_75 = OpConstant %float 0.75
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+)";
+
+ const std::string predefs_after =
+ 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 %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_1 = OpConstant %float 1
+%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %7
+%24 = OpLabel
+%d = OpVariable %_ptr_Function_float Function
+%25 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%26 = OpLoad %float %25
+%27 = OpFOrdEqual %bool %26 %float_0
+OpSelectionMerge %28 None
+OpBranchConditional %27 %29 %30
+%29 = OpLabel
+%31 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%32 = OpLoad %float %31
+%33 = OpFOrdEqual %bool %32 %float_0
+OpSelectionMerge %34 None
+OpBranchConditional %33 %35 %36
+%35 = OpLabel
+OpStore %d %float_0
+OpBranch %34
+%36 = OpLabel
+OpStore %d %float_0_25
+OpBranch %34
+%34 = OpLabel
+OpBranch %28
+%30 = OpLabel
+%37 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%38 = OpLoad %float %37
+%39 = OpFOrdEqual %bool %38 %float_0
+OpSelectionMerge %40 None
+OpBranchConditional %39 %41 %42
+%41 = OpLabel
+OpStore %d %float_0_5
+OpBranch %40
+%42 = OpLabel
+OpStore %d %float_0_75
+OpBranch %40
+%40 = OpLabel
+OpBranch %28
+%28 = OpLabel
+OpStore %OutColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %7
+%24 = OpLabel
+OpBranch %28
+%28 = OpLabel
+OpStore %OutColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateLiveIfThenElse) {
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float t;
+ // if (BaseColor.x == 0)
+ // t = BaseColor.y;
+ // else
+ // t = BaseColor.z;
+ // OutColor = vec4(t);
+ // }
+
+ const std::string assembly =
+ 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 %BaseColor "BaseColor"
+OpName %t "t"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%20 = OpLabel
+%t = OpVariable %_ptr_Function_float Function
+%21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%22 = OpLoad %float %21
+%23 = OpFOrdEqual %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %26
+%25 = OpLabel
+%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%28 = OpLoad %float %27
+OpStore %t %28
+OpBranch %24
+%26 = OpLabel
+%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2
+%30 = OpLoad %float %29
+OpStore %t %30
+OpBranch %24
+%24 = OpLabel
+%31 = OpLoad %float %t
+%32 = OpCompositeConstruct %v4float %31 %31 %31 %31
+OpStore %OutColor %32
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateLiveIfThenElseNested) {
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float t;
+ // if (BaseColor.x == 0)
+ // if (BaseColor.y == 0)
+ // t = 0.0;
+ // else
+ // t = 0.25;
+ // else
+ // if (BaseColor.y == 0)
+ // t = 0.5;
+ // else
+ // t = 0.75;
+ // OutColor = vec4(t);
+ // }
+
+ const std::string assembly =
+ 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 %BaseColor "BaseColor"
+OpName %t "t"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%uint_1 = OpConstant %uint 1
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0_25 = OpConstant %float 0.25
+%float_0_5 = OpConstant %float 0.5
+%float_0_75 = OpConstant %float 0.75
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%22 = OpLabel
+%t = OpVariable %_ptr_Function_float Function
+%23 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%24 = OpLoad %float %23
+%25 = OpFOrdEqual %bool %24 %float_0
+OpSelectionMerge %26 None
+OpBranchConditional %25 %27 %28
+%27 = OpLabel
+%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%30 = OpLoad %float %29
+%31 = OpFOrdEqual %bool %30 %float_0
+OpSelectionMerge %32 None
+OpBranchConditional %31 %33 %34
+%33 = OpLabel
+OpStore %t %float_0
+OpBranch %32
+%34 = OpLabel
+OpStore %t %float_0_25
+OpBranch %32
+%32 = OpLabel
+OpBranch %26
+%28 = OpLabel
+%35 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%36 = OpLoad %float %35
+%37 = OpFOrdEqual %bool %36 %float_0
+OpSelectionMerge %38 None
+OpBranchConditional %37 %39 %40
+%39 = OpLabel
+OpStore %t %float_0_5
+OpBranch %38
+%40 = OpLabel
+OpStore %t %float_0_75
+OpBranch %38
+%38 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%41 = OpLoad %float %t
+%42 = OpCompositeConstruct %v4float %41 %41 %41 %41
+OpStore %OutColor %42
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfWithPhi) {
+ // Note: Assembly hand-optimized from GLSL
+ //
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float t;
+ // if (BaseColor.x == 0)
+ // t = 0.0;
+ // else
+ // t = 1.0;
+ // OutColor = vec4(t);
+ // }
+
+ const std::string assembly =
+ 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 %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %6
+%17 = OpLabel
+%18 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%19 = OpLoad %float %18
+%20 = OpFOrdEqual %bool %19 %float_0
+OpSelectionMerge %21 None
+OpBranchConditional %20 %22 %23
+%22 = OpLabel
+OpBranch %21
+%23 = OpLabel
+OpBranch %21
+%21 = OpLabel
+%24 = OpPhi %float %float_0 %22 %float_1 %23
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %OutColor %25
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfBreak) {
+ // Note: Assembly optimized from GLSL
+ //
+ // #version 450
+ //
+ // layout(location=0) in vec4 InColor;
+ // layout(location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // for (;;) {
+ // f += 2.0;
+ // if (f > 20.0)
+ // break;
+ // }
+ //
+ // OutColor = InColor / f;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %OutColor %InColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %f "f"
+OpName %OutColor "OutColor"
+OpName %InColor "InColor"
+OpDecorate %OutColor Location 0
+OpDecorate %InColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%float_2 = OpConstant %float 2
+%float_20 = OpConstant %float 20
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%InColor = OpVariable %_ptr_Input_v4float Input
+%main = OpFunction %void None %7
+%17 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpBranch %18
+%18 = OpLabel
+OpLoopMerge %19 %20 None
+OpBranch %21
+%21 = OpLabel
+%22 = OpLoad %float %f
+%23 = OpFAdd %float %22 %float_2
+OpStore %f %23
+%24 = OpLoad %float %f
+%25 = OpFOrdGreaterThan %bool %24 %float_20
+OpSelectionMerge %26 None
+OpBranchConditional %25 %27 %26
+%27 = OpLabel
+OpBranch %19
+%26 = OpLabel
+OpBranch %20
+%20 = OpLabel
+OpBranch %18
+%19 = OpLabel
+%28 = OpLoad %v4float %InColor
+%29 = OpLoad %float %f
+%30 = OpCompositeConstruct %v4float %29 %29 %29 %29
+%31 = OpFDiv %v4float %28 %30
+OpStore %OutColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfBreak2) {
+ // Do not eliminate break as conditional branch with merge instruction
+ // Note: SPIR-V edited to add merge instruction before break.
+ //
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++)
+ // s += g_F[i];
+ // o = s;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%25 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+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_10
+OpSelectionMerge %32 None
+OpBranchConditional %31 %32 %27
+%32 = OpLabel
+%33 = OpLoad %int %i
+%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %33
+%35 = OpLoad %float %34
+%36 = OpLoad %float %s
+%37 = OpFAdd %float %36 %35
+OpStore %s %37
+OpBranch %28
+%28 = OpLabel
+%38 = OpLoad %int %i
+%39 = OpIAdd %int %38 %int_1
+OpStore %i %39
+OpBranch %26
+%27 = OpLabel
+%40 = OpLoad %float %s
+OpStore %o %40
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateEntireUselessLoop) {
+ // #version 140
+ // in vec4 BaseColor;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // int g_I ;
+ // } ;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // float df = 0.0;
+ // int i = 0;
+ // while (i < g_I) {
+ // df = df * 0.5;
+ // i = i + 1;
+ // }
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs1 =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+)";
+
+ const std::string names_before =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %df "df"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_I"
+OpName %_ ""
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string names_after =
+ R"(OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+)";
+
+ const std::string predefs2_before =
+ R"(OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%11 = 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
+%_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
+%U_t = OpTypeStruct %int
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%bool = OpTypeBool
+%float_0_5 = OpConstant %float 0.5
+%int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string predefs2_after =
+ R"(%void = OpTypeVoid
+%11 = 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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %11
+%27 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%df = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%28 = OpLoad %v4float %BaseColor
+OpStore %v %28
+OpStore %df %float_0
+OpStore %i %int_0
+OpBranch %29
+%29 = OpLabel
+OpLoopMerge %30 %31 None
+OpBranch %32
+%32 = OpLabel
+%33 = OpLoad %int %i
+%34 = OpAccessChain %_ptr_Uniform_int %_ %int_0
+%35 = OpLoad %int %34
+%36 = OpSLessThan %bool %33 %35
+OpBranchConditional %36 %37 %30
+%37 = OpLabel
+%38 = OpLoad %float %df
+%39 = OpFMul %float %38 %float_0_5
+OpStore %df %39
+%40 = OpLoad %int %i
+%41 = OpIAdd %int %40 %int_1
+OpStore %i %41
+OpBranch %31
+%31 = OpLabel
+OpBranch %29
+%30 = OpLabel
+%42 = OpLoad %v4float %v
+OpStore %gl_FragColor %42
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %11
+%27 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%28 = OpLoad %v4float %BaseColor
+OpStore %v %28
+OpBranch %29
+%29 = OpLabel
+OpBranch %30
+%30 = OpLabel
+%42 = OpLoad %v4float %v
+OpStore %gl_FragColor %42
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs1 + names_before + predefs2_before + func_before,
+ predefs1 + names_after + predefs2_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateBusyLoop) {
+ // Note: SPIR-V edited to replace AtomicAdd(i,0) with AtomicLoad(i)
+ //
+ // #version 450
+ //
+ // layout(std430) buffer I_t
+ // {
+ // int g_I;
+ // int g_I2;
+ // };
+ //
+ // layout(location = 0) out int o;
+ //
+ // void main(void)
+ // {
+ // while (atomicAdd(g_I, 0) == 0) {}
+ // o = g_I2;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %I_t "I_t"
+OpMemberName %I_t 0 "g_I"
+OpMemberName %I_t 1 "g_I2"
+OpName %_ ""
+OpName %o "o"
+OpMemberDecorate %I_t 0 Offset 0
+OpMemberDecorate %I_t 1 Offset 4
+OpDecorate %I_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%I_t = OpTypeStruct %int %int
+%_ptr_Uniform_I_t = OpTypePointer Uniform %I_t
+%_ = OpVariable %_ptr_Uniform_I_t Uniform
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%_ptr_Output_int = OpTypePointer Output %int
+%o = OpVariable %_ptr_Output_int Output
+%main = OpFunction %void None %7
+%18 = OpLabel
+OpBranch %19
+%19 = OpLabel
+OpLoopMerge %20 %21 None
+OpBranch %22
+%22 = OpLabel
+%23 = OpAccessChain %_ptr_Uniform_int %_ %int_0
+%24 = OpAtomicLoad %int %23 %uint_1 %uint_0
+%25 = OpIEqual %bool %24 %int_0
+OpBranchConditional %25 %26 %20
+%26 = OpLabel
+OpBranch %21
+%21 = OpLabel
+OpBranch %19
+%20 = OpLabel
+%27 = OpAccessChain %_ptr_Uniform_int %_ %int_1
+%28 = OpLoad %int %27
+OpStore %o %28
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateLiveLoop) {
+ // Note: SPIR-V optimized
+ //
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++)
+ // s += g_F[i];
+ // o = s;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %8
+%21 = OpLabel
+OpBranch %22
+%22 = OpLabel
+%23 = OpPhi %float %float_0 %21 %24 %25
+%26 = OpPhi %int %int_0 %21 %27 %25
+OpLoopMerge %28 %25 None
+OpBranch %29
+%29 = OpLabel
+%30 = OpSLessThan %bool %26 %int_10
+OpBranchConditional %30 %31 %28
+%31 = OpLabel
+%32 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %26
+%33 = OpLoad %float %32
+%24 = OpFAdd %float %23 %33
+OpBranch %25
+%25 = OpLabel
+%27 = OpIAdd %int %26 %int_1
+OpBranch %22
+%28 = OpLabel
+OpStore %o %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateEntireFunctionBody) {
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // float d;
+ // if (BaseColor.x == 0)
+ // d = BaseColor.y;
+ // else
+ // d = BaseColor.z;
+ // }
+
+ const std::string predefs_before =
+ 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 %BaseColor "BaseColor"
+OpName %d "d"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = 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 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %BaseColor "BaseColor"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %7
+%20 = OpLabel
+%d = OpVariable %_ptr_Function_float Function
+%21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%22 = OpLoad %float %21
+%23 = OpFOrdEqual %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %26
+%25 = OpLabel
+%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1
+%28 = OpLoad %float %27
+OpStore %d %28
+OpBranch %24
+%26 = OpLabel
+%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2
+%30 = OpLoad %float %29
+OpStore %d %30
+OpBranch %24
+%24 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %7
+%20 = OpLabel
+OpBranch %24
+%24 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateUselessInnerLoop) {
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++) {
+ // for (int j=0; j<10; j++) {
+ // }
+ // s += g_F[i];
+ // }
+ // o = s;
+ // }
+
+ const std::string predefs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %j "j"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string predefs_after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%j = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+OpStore %j %int_0
+OpBranch %34
+%34 = OpLabel
+OpLoopMerge %35 %36 None
+OpBranch %37
+%37 = OpLabel
+%38 = OpLoad %int %j
+%39 = OpSLessThan %bool %38 %int_10
+OpBranchConditional %39 %40 %35
+%40 = OpLabel
+OpBranch %36
+%36 = OpLabel
+%41 = OpLoad %int %j
+%42 = OpIAdd %int %41 %int_1
+OpStore %j %42
+OpBranch %34
+%35 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %43
+%45 = OpLoad %float %44
+%46 = OpLoad %float %s
+%47 = OpFAdd %float %46 %45
+OpStore %s %47
+OpBranch %29
+%29 = OpLabel
+%48 = OpLoad %int %i
+%49 = OpIAdd %int %48 %int_1
+OpStore %i %49
+OpBranch %27
+%28 = OpLabel
+%50 = OpLoad %float %s
+OpStore %o %50
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+OpBranch %34
+%34 = OpLabel
+OpBranch %35
+%35 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %43
+%45 = OpLoad %float %44
+%46 = OpLoad %float %s
+%47 = OpFAdd %float %46 %45
+OpStore %s %47
+OpBranch %29
+%29 = OpLabel
+%48 = OpLoad %int %i
+%49 = OpIAdd %int %48 %int_1
+OpStore %i %49
+OpBranch %27
+%28 = OpLabel
+%50 = OpLoad %float %s
+OpStore %o %50
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateUselessNestedLoopWithIf) {
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10][10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++) {
+ // for (int j=0; j<10; j++) {
+ // float t = g_F[i][j];
+ // if (t > 0.0)
+ // s += t;
+ // }
+ // }
+ // o = 0.0;
+ // }
+
+ const std::string predefs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %j "j"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpDecorate %_arr__arr_float_uint_10_uint_10 ArrayStride 40
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%12 = 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
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+%U_t = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string predefs_after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %o "o"
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %12
+%27 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%j = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %28
+%28 = OpLabel
+OpLoopMerge %29 %30 None
+OpBranch %31
+%31 = OpLabel
+%32 = OpLoad %int %i
+%33 = OpSLessThan %bool %32 %int_10
+OpBranchConditional %33 %34 %29
+%34 = OpLabel
+OpStore %j %int_0
+OpBranch %35
+%35 = OpLabel
+OpLoopMerge %36 %37 None
+OpBranch %38
+%38 = OpLabel
+%39 = OpLoad %int %j
+%40 = OpSLessThan %bool %39 %int_10
+OpBranchConditional %40 %41 %36
+%41 = OpLabel
+%42 = OpLoad %int %i
+%43 = OpLoad %int %j
+%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %42 %43
+%45 = OpLoad %float %44
+%46 = OpFOrdGreaterThan %bool %45 %float_0
+OpSelectionMerge %47 None
+OpBranchConditional %46 %48 %47
+%48 = OpLabel
+%49 = OpLoad %float %s
+%50 = OpFAdd %float %49 %45
+OpStore %s %50
+OpBranch %47
+%47 = OpLabel
+OpBranch %37
+%37 = OpLabel
+%51 = OpLoad %int %j
+%52 = OpIAdd %int %51 %int_1
+OpStore %j %52
+OpBranch %35
+%36 = OpLabel
+OpBranch %30
+%30 = OpLabel
+%53 = OpLoad %int %i
+%54 = OpIAdd %int %53 %int_1
+OpStore %i %54
+OpBranch %28
+%29 = OpLabel
+OpStore %o %float_0
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %12
+%27 = OpLabel
+OpBranch %28
+%28 = OpLabel
+OpBranch %29
+%29 = OpLabel
+OpStore %o %float_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateEmptyIfBeforeContinue) {
+ // #version 430
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++) {
+ // s += 1.0;
+ // if (i > s) {}
+ // }
+ // o = s;
+ // }
+
+ const std::string predefs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %3
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpName %main "main"
+OpDecorate %3 Location 0
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%3 = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string predefs_after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %3
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpName %main "main"
+OpDecorate %3 Location 0
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%3 = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %5
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpPhi %float %float_0 %16 %19 %20
+%21 = OpPhi %int %int_0 %16 %22 %20
+OpLoopMerge %23 %20 None
+OpBranch %24
+%24 = OpLabel
+%25 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %25 %26 %23
+%26 = OpLabel
+%19 = OpFAdd %float %18 %float_1
+%27 = OpConvertFToS %int %19
+%28 = OpSGreaterThan %bool %21 %27
+OpSelectionMerge %20 None
+OpBranchConditional %28 %29 %20
+%29 = OpLabel
+OpBranch %20
+%20 = OpLabel
+%22 = OpIAdd %int %21 %int_1
+OpBranch %17
+%23 = OpLabel
+OpStore %3 %18
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %5
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpPhi %float %float_0 %16 %19 %20
+%21 = OpPhi %int %int_0 %16 %22 %20
+OpLoopMerge %23 %20 None
+OpBranch %24
+%24 = OpLabel
+%25 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %25 %26 %23
+%26 = OpLabel
+%19 = OpFAdd %float %18 %float_1
+OpBranch %20
+%20 = OpLabel
+%22 = OpIAdd %int %21 %int_1
+OpBranch %17
+%23 = OpLabel
+OpStore %3 %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ predefs_before + func_before, predefs_after + func_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateLiveNestedLoopWithIf) {
+ // Note: SPIR-V optimized
+ //
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10][10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++) {
+ // for (int j=0; j<10; j++) {
+ // float t = g_F[i][j];
+ // if (t > 0.0)
+ // s += t;
+ // }
+ // }
+ // o = s;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %j "j"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpDecorate %_arr__arr_float_uint_10_uint_10 ArrayStride 40
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%12 = 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
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
+%U_t = OpTypeStruct %_arr__arr_float_uint_10_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %12
+%27 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%j = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %28
+%28 = OpLabel
+OpLoopMerge %29 %30 None
+OpBranch %31
+%31 = OpLabel
+%32 = OpLoad %int %i
+%33 = OpSLessThan %bool %32 %int_10
+OpBranchConditional %33 %34 %29
+%34 = OpLabel
+OpStore %j %int_0
+OpBranch %35
+%35 = OpLabel
+OpLoopMerge %36 %37 None
+OpBranch %38
+%38 = OpLabel
+%39 = OpLoad %int %j
+%40 = OpSLessThan %bool %39 %int_10
+OpBranchConditional %40 %41 %36
+%41 = OpLabel
+%42 = OpLoad %int %i
+%43 = OpLoad %int %j
+%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %42 %43
+%45 = OpLoad %float %44
+%46 = OpFOrdGreaterThan %bool %45 %float_0
+OpSelectionMerge %47 None
+OpBranchConditional %46 %48 %47
+%48 = OpLabel
+%49 = OpLoad %float %s
+%50 = OpFAdd %float %49 %45
+OpStore %s %50
+OpBranch %47
+%47 = OpLabel
+OpBranch %37
+%37 = OpLabel
+%51 = OpLoad %int %j
+%52 = OpIAdd %int %51 %int_1
+OpStore %j %52
+OpBranch %35
+%36 = OpLabel
+OpBranch %30
+%30 = OpLabel
+%53 = OpLoad %int %i
+%54 = OpIAdd %int %53 %int_1
+OpStore %i %54
+OpBranch %28
+%29 = OpLabel
+%55 = OpLoad %float %s
+OpStore %o %55
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfContinue) {
+ // Do not eliminate continue embedded in if construct
+ //
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++) {
+ // if (i % 2 == 0) continue;
+ // s += g_F[i];
+ // }
+ // o = s;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_2 = OpConstant %int 2
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSMod %int %34 %int_2
+%36 = OpIEqual %bool %35 %int_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
+OpBranch %29
+%37 = OpLabel
+%39 = OpLoad %int %i
+%40 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %39
+%41 = OpLoad %float %40
+%42 = OpLoad %float %s
+%43 = OpFAdd %float %42 %41
+OpStore %s %43
+OpBranch %29
+%29 = OpLabel
+%44 = OpLoad %int %i
+%45 = OpIAdd %int %44 %int_1
+OpStore %i %45
+OpBranch %27
+%28 = OpLabel
+%46 = OpLoad %float %s
+OpStore %o %46
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfContinue2) {
+ // Do not eliminate continue not embedded in if construct
+ //
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++) {
+ // if (i % 2 == 0) continue;
+ // s += g_F[i];
+ // }
+ // o = s;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_2 = OpConstant %int 2
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSMod %int %34 %int_2
+%36 = OpIEqual %bool %35 %int_0
+OpBranchConditional %36 %29 %37
+%37 = OpLabel
+%38 = OpLoad %int %i
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %38
+%40 = OpLoad %float %39
+%41 = OpLoad %float %s
+%42 = OpFAdd %float %41 %40
+OpStore %s %42
+OpBranch %29
+%29 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpIAdd %int %43 %int_1
+OpStore %i %44
+OpBranch %27
+%28 = OpLabel
+%45 = OpLoad %float %s
+OpStore %o %45
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+TEST_F(AggressiveDCETest, NoEliminateIfContinue3) {
+ // Do not eliminate continue as conditional branch with merge instruction
+ // Note: SPIR-V edited to add merge instruction before continue.
+ //
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10];
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // float s = 0.0;
+ // for (int i=0; i<10; i++) {
+ // if (i % 2 == 0) continue;
+ // s += g_F[i];
+ // }
+ // o = s;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %s "s"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_2 = OpConstant %int 2
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %10
+%26 = OpLabel
+%s = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %s %float_0
+OpStore %i %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpSLessThan %bool %31 %int_10
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSMod %int %34 %int_2
+%36 = OpIEqual %bool %35 %int_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %29 %37
+%37 = OpLabel
+%38 = OpLoad %int %i
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %38
+%40 = OpLoad %float %39
+%41 = OpLoad %float %s
+%42 = OpFAdd %float %41 %40
+OpStore %s %42
+OpBranch %29
+%29 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpIAdd %int %43 %int_1
+OpStore %i %44
+OpBranch %27
+%28 = OpLabel
+%45 = OpLoad %float %s
+OpStore %o %45
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(assembly, assembly, true, true);
+}
+
+// This is not valid input and ADCE does not support variable pointers and only
+// supports shaders.
+TEST_F(AggressiveDCETest, PointerVariable) {
+ // ADCE is able to handle code that contains a load whose base address
+ // comes from a load and not an OpVariable. I want to see an instruction
+ // removed to be sure that ADCE is not exiting early.
+
+ const std::string before =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpMemberDecorate %_struct_6 0 Offset 0
+OpDecorate %_struct_6 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+OpDecorate %8 DescriptorSet 0
+OpDecorate %8 Binding 1
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_struct_6 = OpTypeStruct %int
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%8 = OpVariable %_ptr_Uniform__struct_6 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%26 = OpLoad %_ptr_Uniform__struct_5 %24
+%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0
+%28 = OpLoad %v4float %27
+%29 = OpCopyObject %v4float %28
+OpStore %2 %28
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%25 = OpLoad %_ptr_Uniform__struct_5 %24
+%26 = OpAccessChain %_ptr_Uniform_v4float %25 %int_0 %uint_0 %int_0
+%27 = OpLoad %v4float %26
+OpStore %2 %27
+OpReturn
+OpFunctionEnd
+)";
+
+ // The input is not valid and ADCE only supports shaders, but not variable
+ // pointers. Workaround this by enabling relaxed logical pointers in the
+ // validator.
+ ValidatorOptions()->relax_logical_pointer = true;
+ SinglePassRunAndCheck<AggressiveDCEPass>(before, after, true, true);
+}
+
+// %dead is unused. Make sure we remove it along with its name.
+TEST_F(AggressiveDCETest, RemoveUnreferenced) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%dead = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(before, after, true, true);
+}
+
+// Delete %dead because it is unreferenced. Then %initializer becomes
+// unreferenced, so remove it as well.
+TEST_F(AggressiveDCETest, RemoveUnreferencedWithInit1) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%dead = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(before, after, true, true);
+}
+
+// Keep %live because it is used, and its initializer.
+TEST_F(AggressiveDCETest, KeepReferenced) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %live "live"
+OpName %initializer "initializer"
+OpName %output "output"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%live = OpVariable %_ptr_Private_float Private %initializer
+%_ptr_Output_float = OpTypePointer Output %float
+%output = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %live
+OpStore %output %10
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(before, before, true, true);
+}
+
+// This test that the decoration associated with a variable are removed when the
+// variable is removed.
+TEST_F(AggressiveDCETest, RemoveVariableAndDecorations) {
+ const std::string before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %B "B"
+OpMemberName %B 0 "a"
+OpName %Bdat "Bdat"
+OpMemberDecorate %B 0 Offset 0
+OpDecorate %B BufferBlock
+OpDecorate %Bdat DescriptorSet 0
+OpDecorate %Bdat Binding 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%B = OpTypeStruct %uint
+%_ptr_Uniform_B = OpTypePointer Uniform %B
+%Bdat = OpVariable %_ptr_Uniform_B Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(before, after, true, true);
+}
+
+TEST_F(AggressiveDCETest, DeadNestedSwitch) {
+ const std::string text = R"(
+; CHECK: OpLabel
+; CHECK: OpBranch [[block:%\w+]]
+; CHECK-NOT: OpSwitch
+; CHECK-NEXT: [[block]] = OpLabel
+; CHECK: OpBranch [[block:%\w+]]
+; CHECK-NOT: OpSwitch
+; CHECK-NEXT: [[block]] = OpLabel
+; CHECK-NEXT: OpStore
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func" %x
+OpExecutionMode %func OriginUpperLeft
+OpName %func "func"
+%void = OpTypeVoid
+%1 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_ptr_Output = OpTypePointer Output %uint
+%uint_ptr_Input = OpTypePointer Input %uint
+%x = OpVariable %uint_ptr_Output Output
+%a = OpVariable %uint_ptr_Input Input
+%func = OpFunction %void None %1
+%entry = OpLabel
+OpBranch %header
+%header = OpLabel
+%ld = OpLoad %uint %a
+OpLoopMerge %merge %continue None
+OpBranch %postheader
+%postheader = OpLabel
+; This switch doesn't require an OpSelectionMerge and is nested in the dead loop.
+OpSwitch %ld %merge 0 %extra 1 %continue
+%extra = OpLabel
+OpBranch %continue
+%continue = OpLabel
+OpBranch %header
+%merge = OpLabel
+OpStore %x %uint_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, LiveNestedSwitch) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func" %3 %10
+OpExecutionMode %func OriginUpperLeft
+OpName %func "func"
+%void = OpTypeVoid
+%1 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%_ptr_Input_uint = OpTypePointer Input %uint
+%3 = OpVariable %_ptr_Output_uint Output
+%10 = OpVariable %_ptr_Input_uint Input
+%func = OpFunction %void None %1
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%13 = OpLoad %uint %10
+OpLoopMerge %14 %15 None
+OpBranch %16
+%16 = OpLabel
+OpSwitch %13 %14 0 %17 1 %15
+%17 = OpLabel
+OpStore %3 %uint_1
+OpBranch %15
+%15 = OpLabel
+OpBranch %12
+%14 = OpLabel
+OpStore %3 %uint_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(text, text, false, true);
+}
+
+TEST_F(AggressiveDCETest, BasicDeleteDeadFunction) {
+ // The function Dead should be removed because it is never called.
+ const std::vector<const char*> common_code = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %Live \"Live\"",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "%16 = OpFunctionCall %void %Live",
+ "%17 = OpFunctionCall %void %Live",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ const std::vector<const char*> dead_function = {
+ // clang-format off
+ "%Dead = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ JoinAllInsts(Concat(common_code, dead_function)),
+ JoinAllInsts(common_code), /* skip_nop = */ true);
+}
+
+TEST_F(AggressiveDCETest, BasicKeepLiveFunction) {
+ // Everything is reachable from an entry point, so no functions should be
+ // deleted.
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %Live1 \"Live1\"",
+ "OpName %Live2 \"Live2\"",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "%16 = OpFunctionCall %void %Live2",
+ "%17 = OpFunctionCall %void %Live1",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live1 = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live2 = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ std::string assembly = JoinAllInsts(text);
+ auto result = SinglePassRunAndDisassemble<AggressiveDCEPass>(
+ assembly, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+ EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(AggressiveDCETest, BasicRemoveDecorationsAndNames) {
+ // We want to remove the names and decorations associated with results that
+ // are removed. This test will check for that.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ OpName %Dead "Dead"
+ OpName %x "x"
+ OpName %y "y"
+ OpName %z "z"
+ OpDecorate %x RelaxedPrecision
+ OpDecorate %y RelaxedPrecision
+ OpDecorate %z RelaxedPrecision
+ OpDecorate %6 RelaxedPrecision
+ OpDecorate %7 RelaxedPrecision
+ OpDecorate %8 RelaxedPrecision
+ %void = OpTypeVoid
+ %10 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+ %float_1 = OpConstant %float 1
+ %main = OpFunction %void None %10
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %10
+ %15 = OpLabel
+ %x = OpVariable %_ptr_Function_float Function
+ %y = OpVariable %_ptr_Function_float Function
+ %z = OpVariable %_ptr_Function_float Function
+ OpStore %x %float_1
+ OpStore %y %float_1
+ %6 = OpLoad %float %x
+ %7 = OpLoad %float %y
+ %8 = OpFAdd %float %6 %7
+ OpStore %z %8
+ OpReturn
+ OpFunctionEnd)";
+
+ const std::string expected_output = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%main = OpFunction %void None %10
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(text, expected_output,
+ /* skip_nop = */ true);
+}
+
+TEST_F(AggressiveDCETest, BasicAllDeadConstants) {
+ const std::string text = R"(
+ ; CHECK-NOT: OpConstant
+ OpCapability Shader
+ OpCapability Float64
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %false = OpConstantFalse %bool
+ %int = OpTypeInt 32 1
+ %9 = OpConstant %int 1
+ %uint = OpTypeInt 32 0
+ %11 = OpConstant %uint 2
+ %float = OpTypeFloat 32
+ %13 = OpConstant %float 3.1415
+ %double = OpTypeFloat 64
+ %15 = OpConstant %double 3.14159265358979
+ %main = OpFunction %void None %4
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, BasicNoneDeadConstants) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\" %btv %bfv %iv %uv %fv %dv",
+ "OpName %main \"main\"",
+ "OpName %btv \"btv\"",
+ "OpName %bfv \"bfv\"",
+ "OpName %iv \"iv\"",
+ "OpName %uv \"uv\"",
+ "OpName %fv \"fv\"",
+ "OpName %dv \"dv\"",
+ "%void = OpTypeVoid",
+ "%10 = OpTypeFunction %void",
+ "%bool = OpTypeBool",
+ "%_ptr_Output_bool = OpTypePointer Output %bool",
+ "%true = OpConstantTrue %bool",
+ "%false = OpConstantFalse %bool",
+ "%int = OpTypeInt 32 1",
+ "%_ptr_Output_int = OpTypePointer Output %int",
+ "%int_1 = OpConstant %int 1",
+ "%uint = OpTypeInt 32 0",
+ "%_ptr_Output_uint = OpTypePointer Output %uint",
+ "%uint_2 = OpConstant %uint 2",
+ "%float = OpTypeFloat 32",
+ "%_ptr_Output_float = OpTypePointer Output %float",
+ "%float_3_1415 = OpConstant %float 3.1415",
+ "%double = OpTypeFloat 64",
+ "%_ptr_Output_double = OpTypePointer Output %double",
+ "%double_3_14159265358979 = OpConstant %double 3.14159265358979",
+ "%btv = OpVariable %_ptr_Output_bool Output",
+ "%bfv = OpVariable %_ptr_Output_bool Output",
+ "%iv = OpVariable %_ptr_Output_int Output",
+ "%uv = OpVariable %_ptr_Output_uint Output",
+ "%fv = OpVariable %_ptr_Output_float Output",
+ "%dv = OpVariable %_ptr_Output_double Output",
+ "%main = OpFunction %void None %10",
+ "%27 = OpLabel",
+ "OpStore %btv %true",
+ "OpStore %bfv %false",
+ "OpStore %iv %int_1",
+ "OpStore %uv %uint_2",
+ "OpStore %fv %float_3_1415",
+ "OpStore %dv %double_3_14159265358979",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ // All constants are used, so none of them should be eliminated.
+ SinglePassRunAndCheck<AggressiveDCEPass>(
+ JoinAllInsts(text), JoinAllInsts(text), /* skip_nop = */ true);
+}
+
+struct AggressiveEliminateDeadConstantTestCase {
+ // Type declarations and constants that should be kept.
+ std::vector<std::string> used_consts;
+ // Instructions that refer to constants, this is added to create uses for
+ // some constants so they won't be treated as dead constants.
+ std::vector<std::string> main_insts;
+ // Dead constants that should be removed.
+ std::vector<std::string> dead_consts;
+ // Expectations
+ std::vector<std::string> checks;
+};
+
+// All types that are potentially required in
+// AggressiveEliminateDeadConstantTest.
+const std::vector<std::string> CommonTypes = {
+ // clang-format off
+ // scalar types
+ "%bool = OpTypeBool",
+ "%uint = OpTypeInt 32 0",
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%double = OpTypeFloat 64",
+ // vector types
+ "%v2bool = OpTypeVector %bool 2",
+ "%v2uint = OpTypeVector %uint 2",
+ "%v2int = OpTypeVector %int 2",
+ "%v3int = OpTypeVector %int 3",
+ "%v4int = OpTypeVector %int 4",
+ "%v2float = OpTypeVector %float 2",
+ "%v3float = OpTypeVector %float 3",
+ "%v2double = OpTypeVector %double 2",
+ // variable pointer types
+ "%_pf_bool = OpTypePointer Output %bool",
+ "%_pf_uint = OpTypePointer Output %uint",
+ "%_pf_int = OpTypePointer Output %int",
+ "%_pf_float = OpTypePointer Output %float",
+ "%_pf_double = OpTypePointer Output %double",
+ "%_pf_v2int = OpTypePointer Output %v2int",
+ "%_pf_v3int = OpTypePointer Output %v3int",
+ "%_pf_v2float = OpTypePointer Output %v2float",
+ "%_pf_v3float = OpTypePointer Output %v3float",
+ "%_pf_v2double = OpTypePointer Output %v2double",
+ // struct types
+ "%inner_struct = OpTypeStruct %bool %int %float %double",
+ "%outer_struct = OpTypeStruct %inner_struct %int %double",
+ "%flat_struct = OpTypeStruct %bool %int %float %double",
+ // clang-format on
+};
+
+using AggressiveEliminateDeadConstantTest =
+ PassTest<::testing::TestWithParam<AggressiveEliminateDeadConstantTestCase>>;
+
+TEST_P(AggressiveEliminateDeadConstantTest, Custom) {
+ auto& tc = GetParam();
+ AssemblyBuilder builder;
+ builder.AppendTypesConstantsGlobals(CommonTypes)
+ .AppendTypesConstantsGlobals(tc.used_consts)
+ .AppendInMain(tc.main_insts);
+ const std::string expected = builder.GetCode();
+ builder.AppendTypesConstantsGlobals(tc.dead_consts);
+ builder.PrependPreamble(tc.checks);
+ const std::string assembly_with_dead_const = builder.GetCode();
+
+ // Do not enable validation. As the input code is invalid from the base
+ // tests (ported from other passes).
+ SinglePassRunAndMatch<AggressiveDCEPass>(assembly_with_dead_const, false);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ScalarTypeConstants, AggressiveEliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
+ // clang-format off
+ // Scalar type constants, one dead constant and one used constant.
+ {
+ /* .used_consts = */
+ {
+ "%used_const_int = OpConstant %int 1",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Output",
+ "OpStore %int_var %used_const_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_int = OpConstant %int 1",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[const:%\\w+]] = OpConstant %int 1",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ {
+ /* .used_consts = */
+ {
+ "%used_const_uint = OpConstant %uint 1",
+ },
+ /* .main_insts = */
+ {
+ "%uint_var = OpVariable %_pf_uint Output",
+ "OpStore %uint_var %used_const_uint",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_uint = OpConstant %uint 1",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[const:%\\w+]] = OpConstant %uint 1",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ {
+ /* .used_consts = */
+ {
+ "%used_const_float = OpConstant %float 3.1415",
+ },
+ /* .main_insts = */
+ {
+ "%float_var = OpVariable %_pf_float Output",
+ "OpStore %float_var %used_const_float",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_float = OpConstant %float 3.1415",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[const:%\\w+]] = OpConstant %float 3.1415",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ {
+ /* .used_consts = */
+ {
+ "%used_const_double = OpConstant %double 3.14",
+ },
+ /* .main_insts = */
+ {
+ "%double_var = OpVariable %_pf_double Output",
+ "OpStore %double_var %used_const_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_double = OpConstant %double 3.14",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[const:%\\w+]] = OpConstant %double 3.14",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ VectorTypeConstants, AggressiveEliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
+ // clang-format off
+ // Tests eliminating dead constant type ivec2. One dead constant vector
+ // and one used constant vector, each built from its own group of
+ // scalar constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_int_x = OpConstant %int 1",
+ "%used_int_y = OpConstant %int 2",
+ "%used_v2int = OpConstantComposite %v2int %used_int_x %used_int_y",
+ },
+ /* .main_insts = */
+ {
+ "%v2int_var = OpVariable %_pf_v2int Output",
+ "OpStore %v2int_var %used_v2int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int_x = OpConstant %int 1",
+ "%dead_int_y = OpConstant %int 2",
+ "%dead_v2int = OpConstantComposite %v2int %dead_int_x %dead_int_y",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[constx:%\\w+]] = OpConstant %int 1",
+ "; CHECK: [[consty:%\\w+]] = OpConstant %int 2",
+ "; CHECK: [[const:%\\w+]] = OpConstantComposite %v2int [[constx]] [[consty]]",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ // Tests eliminating dead constant ivec3. One dead constant vector and
+ // one used constant vector. But both built from a same group of
+ // scalar constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_int_x = OpConstant %int 1",
+ "%used_int_y = OpConstant %int 2",
+ "%used_int_z = OpConstant %int 3",
+ "%used_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z",
+ },
+ /* .main_insts = */
+ {
+ "%v3int_var = OpVariable %_pf_v3int Output",
+ "OpStore %v3int_var %used_v3int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[constx:%\\w+]] = OpConstant %int 1",
+ "; CHECK: [[consty:%\\w+]] = OpConstant %int 2",
+ "; CHECK: [[constz:%\\w+]] = OpConstant %int 3",
+ "; CHECK: [[const:%\\w+]] = OpConstantComposite %v3int [[constx]] [[consty]] [[constz]]",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ // Tests eliminating dead constant vec2. One dead constant vector and
+ // one used constant vector. Each built from its own group of scalar
+ // constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_float_x = OpConstant %float 3.1415",
+ "%used_float_y = OpConstant %float 4.13",
+ "%used_v2float = OpConstantComposite %v2float %used_float_x %used_float_y",
+ },
+ /* .main_insts = */
+ {
+ "%v2float_var = OpVariable %_pf_v2float Output",
+ "OpStore %v2float_var %used_v2float",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_float_x = OpConstant %float 3.1415",
+ "%dead_float_y = OpConstant %float 4.13",
+ "%dead_v2float = OpConstantComposite %v2float %dead_float_x %dead_float_y",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[constx:%\\w+]] = OpConstant %float 3.1415",
+ "; CHECK: [[consty:%\\w+]] = OpConstant %float 4.13",
+ "; CHECK: [[const:%\\w+]] = OpConstantComposite %v2float [[constx]] [[consty]]",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ // Tests eliminating dead constant vec3. One dead constant vector and
+ // one used constant vector. Both built from a same group of scalar
+ // constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_float_x = OpConstant %float 3.1415",
+ "%used_float_y = OpConstant %float 4.25",
+ "%used_float_z = OpConstant %float 4.75",
+ "%used_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z",
+ },
+ /* .main_insts = */
+ {
+ "%v3float_var = OpVariable %_pf_v3float Output",
+ "OpStore %v3float_var %used_v3float",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[constx:%\\w+]] = OpConstant %float 3.1415",
+ "; CHECK: [[consty:%\\w+]] = OpConstant %float 4.25",
+ "; CHECK: [[constz:%\\w+]] = OpConstant %float 4.75",
+ "; CHECK: [[const:%\\w+]] = OpConstantComposite %v3float [[constx]] [[consty]]",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[const]]",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ StructTypeConstants, AggressiveEliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
+ // clang-format off
+ // A plain struct type dead constants. All of its components are dead
+ // constants too.
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_int = OpConstant %int 1",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_double = OpConstant %double 3.14159265358979",
+ "%dead_struct = OpConstantComposite %flat_struct %dead_bool %dead_int %dead_float %dead_double",
+ },
+ /* .checks = */
+ {
+ "; CHECK-NOT: OpConstant",
+ },
+ },
+ // A plain struct type dead constants. Some of its components are dead
+ // constants while others are not.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 1",
+ "%used_double = OpConstant %double 3.14159265358979",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Output",
+ "OpStore %int_var %used_int",
+ "%double_var = OpVariable %_pf_double Output",
+ "OpStore %double_var %used_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_struct = OpConstantComposite %flat_struct %dead_bool %used_int %dead_float %used_double",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+ "; CHECK: [[double:%\\w+]] = OpConstant %double 3.14159265358979",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[int]]",
+ "; CHECK: OpStore {{%\\w+}} [[double]]",
+ },
+ },
+ // A nesting struct type dead constants. All components of both outer
+ // and inner structs are dead and should be removed after dead constant
+ // elimination.
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_int = OpConstant %int 1",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_double = OpConstant %double 3.1415926535",
+ "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %dead_int %dead_float %dead_double",
+ "%dead_int2 = OpConstant %int 2",
+ "%dead_double2 = OpConstant %double 1.428571428514",
+ "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int2 %dead_double2",
+ },
+ /* .checks = */
+ {
+ "; CHECK-NOT: OpConstant",
+ },
+ },
+ // A nesting struct type dead constants. Some of its components are
+ // dead constants while others are not.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 1",
+ "%used_double = OpConstant %double 3.14159265358979",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Output",
+ "OpStore %int_var %used_int",
+ "%double_var = OpVariable %_pf_double Output",
+ "OpStore %double_var %used_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %used_int %dead_float %used_double",
+ "%dead_int = OpConstant %int 2",
+ "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int %used_double",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+ "; CHECK: [[double:%\\w+]] = OpConstant %double 3.14159265358979",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[int]]",
+ "; CHECK: OpStore {{%\\w+}} [[double]]",
+ },
+ },
+ // A nesting struct case. The inner struct is used while the outer struct is not
+ {
+ /* .used_const = */
+ {
+ "%used_bool = OpConstantTrue %bool",
+ "%used_int = OpConstant %int 1",
+ "%used_float = OpConstant %float 1.23",
+ "%used_double = OpConstant %double 1.2345678901234",
+ "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+ },
+ /* .main_insts = */
+ {
+ "%bool_var = OpVariable %_pf_bool Output",
+ "%bool_from_inner_struct = OpCompositeExtract %bool %used_inner_struct 0",
+ "OpStore %bool_var %bool_from_inner_struct",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int = OpConstant %int 2",
+ "%dead_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %dead_int %used_double"
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[bool:%\\w+]] = OpConstantTrue",
+ "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+ "; CHECK: [[float:%\\w+]] = OpConstant %float 1.23",
+ "; CHECK: [[double:%\\w+]] = OpConstant %double 1.2345678901234",
+ "; CHECK: [[struct:%\\w+]] = OpConstantComposite %inner_struct [[bool]] [[int]] [[float]] [[double]]",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpCompositeExtract %bool [[struct]]",
+ }
+ },
+ // A nesting struct case. The outer struct is used, so the inner struct should not
+ // be removed even though it is not used anywhere.
+ {
+ /* .used_const = */
+ {
+ "%used_bool = OpConstantTrue %bool",
+ "%used_int = OpConstant %int 1",
+ "%used_float = OpConstant %float 1.23",
+ "%used_double = OpConstant %double 1.2345678901234",
+ "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+ "%used_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double"
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Output",
+ "%int_from_outer_struct = OpCompositeExtract %int %used_outer_struct 1",
+ "OpStore %int_var %int_from_outer_struct",
+ },
+ /* .dead_consts = */ {},
+ /* .checks = */
+ {
+ "; CHECK: [[bool:%\\w+]] = OpConstantTrue %bool",
+ "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+ "; CHECK: [[float:%\\w+]] = OpConstant %float 1.23",
+ "; CHECK: [[double:%\\w+]] = OpConstant %double 1.2345678901234",
+ "; CHECK: [[inner_struct:%\\w+]] = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+ "; CHECK: [[outer_struct:%\\w+]] = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double",
+ "; CHECK: OpCompositeExtract %int [[outer_struct]]",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ ScalarTypeSpecConstants, AggressiveEliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
+ // clang-format off
+ // All scalar type spec constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_bool = OpSpecConstantTrue %bool",
+ "%used_uint = OpSpecConstant %uint 2",
+ "%used_int = OpSpecConstant %int 2",
+ "%used_float = OpSpecConstant %float 2.5",
+ "%used_double = OpSpecConstant %double 1.428571428514",
+ },
+ /* .main_insts = */
+ {
+ "%bool_var = OpVariable %_pf_bool Output",
+ "%uint_var = OpVariable %_pf_uint Output",
+ "%int_var = OpVariable %_pf_int Output",
+ "%float_var = OpVariable %_pf_float Output",
+ "%double_var = OpVariable %_pf_double Output",
+ "OpStore %bool_var %used_bool",
+ "OpStore %uint_var %used_uint",
+ "OpStore %int_var %used_int",
+ "OpStore %float_var %used_float",
+ "OpStore %double_var %used_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpSpecConstantTrue %bool",
+ "%dead_uint = OpSpecConstant %uint 2",
+ "%dead_int = OpSpecConstant %int 2",
+ "%dead_float = OpSpecConstant %float 2.5",
+ "%dead_double = OpSpecConstant %double 1.428571428514",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[bool:%\\w+]] = OpSpecConstantTrue %bool",
+ "; CHECK: [[uint:%\\w+]] = OpSpecConstant %uint 2",
+ "; CHECK: [[int:%\\w+]] = OpSpecConstant %int 2",
+ "; CHECK: [[float:%\\w+]] = OpSpecConstant %float 2.5",
+ "; CHECK: [[double:%\\w+]] = OpSpecConstant %double 1.428571428514",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK: OpStore {{%\\w+}} [[bool]]",
+ "; CHECK: OpStore {{%\\w+}} [[uint]]",
+ "; CHECK: OpStore {{%\\w+}} [[int]]",
+ "; CHECK: OpStore {{%\\w+}} [[float]]",
+ "; CHECK: OpStore {{%\\w+}} [[double]]",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ VectorTypeSpecConstants, AggressiveEliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
+ // clang-format off
+ // Bool vector type spec constants. One vector has all component dead,
+ // another vector has one dead boolean and one used boolean.
+ {
+ /* .used_consts = */
+ {
+ "%used_bool = OpSpecConstantTrue %bool",
+ },
+ /* .main_insts = */
+ {
+ "%bool_var = OpVariable %_pf_bool Output",
+ "OpStore %bool_var %used_bool",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpSpecConstantFalse %bool",
+ "%dead_bool_vec1 = OpSpecConstantComposite %v2bool %dead_bool %dead_bool",
+ "%dead_bool_vec2 = OpSpecConstantComposite %v2bool %dead_bool %used_bool",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[bool:%\\w+]] = OpSpecConstantTrue %bool",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK: OpStore {{%\\w+}} [[bool]]",
+ },
+ },
+
+ // Uint vector type spec constants. One vector has all component dead,
+ // another vector has one dead unsigend integer and one used unsigned
+ // integer.
+ {
+ /* .used_consts = */
+ {
+ "%used_uint = OpSpecConstant %uint 3",
+ },
+ /* .main_insts = */
+ {
+ "%uint_var = OpVariable %_pf_uint Output",
+ "OpStore %uint_var %used_uint",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_uint = OpSpecConstant %uint 1",
+ "%dead_uint_vec1 = OpSpecConstantComposite %v2uint %dead_uint %dead_uint",
+ "%dead_uint_vec2 = OpSpecConstantComposite %v2uint %dead_uint %used_uint",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[uint:%\\w+]] = OpSpecConstant %uint 3",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK: OpStore {{%\\w+}} [[uint]]",
+ },
+ },
+
+ // Int vector type spec constants. One vector has all component dead,
+ // another vector has one dead integer and one used integer.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpSpecConstant %int 3",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Output",
+ "OpStore %int_var %used_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int = OpSpecConstant %int 1",
+ "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_int %dead_int",
+ "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_int %used_int",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[int:%\\w+]] = OpSpecConstant %int 3",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK: OpStore {{%\\w+}} [[int]]",
+ },
+ },
+
+ // Int vector type spec constants built with both spec constants and
+ // front-end constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_spec_int = OpSpecConstant %int 3",
+ "%used_front_end_int = OpConstant %int 3",
+ },
+ /* .main_insts = */
+ {
+ "%int_var1 = OpVariable %_pf_int Output",
+ "OpStore %int_var1 %used_spec_int",
+ "%int_var2 = OpVariable %_pf_int Output",
+ "OpStore %int_var2 %used_front_end_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_spec_int = OpSpecConstant %int 1",
+ "%dead_front_end_int = OpConstant %int 1",
+ // Dead front-end and dead spec constants
+ "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_spec_int %dead_front_end_int",
+ // Used front-end and dead spec constants
+ "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_spec_int %used_front_end_int",
+ // Dead front-end and used spec constants
+ "%dead_int_vec3 = OpSpecConstantComposite %v2int %dead_front_end_int %used_spec_int",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[int1:%\\w+]] = OpSpecConstant %int 3",
+ "; CHECK: [[int2:%\\w+]] = OpConstant %int 3",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK: OpStore {{%\\w+}} [[int1]]",
+ "; CHECK: OpStore {{%\\w+}} [[int2]]",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ SpecConstantOp, AggressiveEliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
+ // clang-format off
+ // Cast operations: uint <-> int <-> bool
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ // Assistant constants, only used in dead spec constant
+ // operations.
+ "%signed_zero = OpConstant %int 0",
+ "%signed_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero",
+ "%unsigned_zero = OpConstant %uint 0",
+ "%unsigned_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+ "%signed_one = OpConstant %int 1",
+ "%signed_one_vec = OpConstantComposite %v2int %signed_one %signed_one",
+ "%unsigned_one = OpConstant %uint 1",
+ "%unsigned_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+
+ // Spec constants that support casting to each other.
+ "%dead_bool = OpSpecConstantTrue %bool",
+ "%dead_uint = OpSpecConstant %uint 1",
+ "%dead_int = OpSpecConstant %int 2",
+ "%dead_bool_vec = OpSpecConstantComposite %v2bool %dead_bool %dead_bool",
+ "%dead_uint_vec = OpSpecConstantComposite %v2uint %dead_uint %dead_uint",
+ "%dead_int_vec = OpSpecConstantComposite %v2int %dead_int %dead_int",
+
+ // Scalar cast to boolean spec constant.
+ "%int_to_bool = OpSpecConstantOp %bool INotEqual %dead_int %signed_zero",
+ "%uint_to_bool = OpSpecConstantOp %bool INotEqual %dead_uint %unsigned_zero",
+
+ // Vector cast to boolean spec constant.
+ "%int_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_int_vec %signed_zero_vec",
+ "%uint_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_uint_vec %unsigned_zero_vec",
+
+ // Scalar cast to int spec constant.
+ "%bool_to_int = OpSpecConstantOp %int Select %dead_bool %signed_one %signed_zero",
+ "%uint_to_int = OpSpecConstantOp %uint IAdd %dead_uint %unsigned_zero",
+
+ // Vector cast to int spec constant.
+ "%bool_to_int_vec = OpSpecConstantOp %v2int Select %dead_bool_vec %signed_one_vec %signed_zero_vec",
+ "%uint_to_int_vec = OpSpecConstantOp %v2uint IAdd %dead_uint_vec %unsigned_zero_vec",
+
+ // Scalar cast to uint spec constant.
+ "%bool_to_uint = OpSpecConstantOp %uint Select %dead_bool %unsigned_one %unsigned_zero",
+ "%int_to_uint_vec = OpSpecConstantOp %uint IAdd %dead_int %signed_zero",
+
+ // Vector cast to uint spec constant.
+ "%bool_to_uint_vec = OpSpecConstantOp %v2uint Select %dead_bool_vec %unsigned_one_vec %unsigned_zero_vec",
+ "%int_to_uint = OpSpecConstantOp %v2uint IAdd %dead_int_vec %signed_zero_vec",
+ },
+ /* .checks = */
+ {
+ "; CHECK-NOT: OpConstant",
+ "; CHECK-NOT: OpSpecConstant",
+ },
+ },
+
+ // Add, sub, mul, div, rem.
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ "%dead_spec_int_a = OpSpecConstant %int 1",
+ "%dead_spec_int_a_vec = OpSpecConstantComposite %v2int %dead_spec_int_a %dead_spec_int_a",
+
+ "%dead_spec_int_b = OpSpecConstant %int 2",
+ "%dead_spec_int_b_vec = OpSpecConstantComposite %v2int %dead_spec_int_b %dead_spec_int_b",
+
+ "%dead_const_int_c = OpConstant %int 3",
+ "%dead_const_int_c_vec = OpConstantComposite %v2int %dead_const_int_c %dead_const_int_c",
+
+ // Add
+ "%add_a_b = OpSpecConstantOp %int IAdd %dead_spec_int_a %dead_spec_int_b",
+ "%add_a_b_vec = OpSpecConstantOp %v2int IAdd %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Sub
+ "%sub_a_b = OpSpecConstantOp %int ISub %dead_spec_int_a %dead_spec_int_b",
+ "%sub_a_b_vec = OpSpecConstantOp %v2int ISub %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Mul
+ "%mul_a_b = OpSpecConstantOp %int IMul %dead_spec_int_a %dead_spec_int_b",
+ "%mul_a_b_vec = OpSpecConstantOp %v2int IMul %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Div
+ "%div_a_b = OpSpecConstantOp %int SDiv %dead_spec_int_a %dead_spec_int_b",
+ "%div_a_b_vec = OpSpecConstantOp %v2int SDiv %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Bitwise Xor
+ "%xor_a_b = OpSpecConstantOp %int BitwiseXor %dead_spec_int_a %dead_spec_int_b",
+ "%xor_a_b_vec = OpSpecConstantOp %v2int BitwiseXor %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Scalar Comparison
+ "%less_a_b = OpSpecConstantOp %bool SLessThan %dead_spec_int_a %dead_spec_int_b",
+ },
+ /* .checks = */
+ {
+ "; CHECK-NOT: OpConstant",
+ "; CHECK-NOT: OpSpecConstant",
+ },
+ },
+
+ // Vectors without used swizzles should be removed.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 3",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Output",
+ "OpStore %int_var %used_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int = OpConstant %int 3",
+
+ "%dead_spec_int_a = OpSpecConstant %int 1",
+ "%vec_a = OpSpecConstantComposite %v4int %dead_spec_int_a %dead_spec_int_a %dead_int %dead_int",
+
+ "%dead_spec_int_b = OpSpecConstant %int 2",
+ "%vec_b = OpSpecConstantComposite %v4int %dead_spec_int_b %dead_spec_int_b %used_int %used_int",
+
+ // Extract scalar
+ "%a_x = OpSpecConstantOp %int CompositeExtract %vec_a 0",
+ "%b_x = OpSpecConstantOp %int CompositeExtract %vec_b 0",
+
+ // Extract vector
+ "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1",
+ "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1",
+ },
+ /* .checks = */
+ {
+ "; CHECK: [[int:%\\w+]] = OpConstant %int 3",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK: OpStore {{%\\w+}} [[int]]",
+ },
+ },
+ // Vectors with used swizzles should not be removed.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 3",
+ "%used_spec_int_a = OpSpecConstant %int 1",
+ "%used_spec_int_b = OpSpecConstant %int 2",
+ // Create vectors
+ "%vec_a = OpSpecConstantComposite %v4int %used_spec_int_a %used_spec_int_a %used_int %used_int",
+ "%vec_b = OpSpecConstantComposite %v4int %used_spec_int_b %used_spec_int_b %used_int %used_int",
+ // Extract vector
+ "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1",
+ "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1",
+ },
+ /* .main_insts = */
+ {
+ "%v2int_var_a = OpVariable %_pf_v2int Output",
+ "%v2int_var_b = OpVariable %_pf_v2int Output",
+ "OpStore %v2int_var_a %a_xy",
+ "OpStore %v2int_var_b %b_xy",
+ },
+ /* .dead_consts = */ {},
+ /* .checks = */
+ {
+ "; CHECK: [[int:%\\w+]] = OpConstant %int 3",
+ "; CHECK: [[a:%\\w+]] = OpSpecConstant %int 1",
+ "; CHECK: [[b:%\\w+]] = OpSpecConstant %int 2",
+ "; CHECK: [[veca:%\\w+]] = OpSpecConstantComposite %v4int [[a]] [[a]] [[int]] [[int]]",
+ "; CHECK: [[vecb:%\\w+]] = OpSpecConstantComposite %v4int [[b]] [[b]] [[int]] [[int]]",
+ "; CHECK: [[exa:%\\w+]] = OpSpecConstantOp %v2int VectorShuffle [[veca]] [[veca]] 0 1",
+ "; CHECK: [[exb:%\\w+]] = OpSpecConstantOp %v2int VectorShuffle [[vecb]] [[vecb]] 0 1",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK: OpStore {{%\\w+}} [[exa]]",
+ "; CHECK: OpStore {{%\\w+}} [[exb]]",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ LongDefUseChain, AggressiveEliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<AggressiveEliminateDeadConstantTestCase>({
+ // clang-format off
+ // Long Def-Use chain with binary operations.
+ {
+ /* .used_consts = */
+ {
+ "%array_size = OpConstant %int 4",
+ "%type_arr_int_4 = OpTypeArray %int %array_size",
+ "%used_int_0 = OpConstant %int 100",
+ "%used_int_1 = OpConstant %int 1",
+ "%used_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_1",
+ "%used_int_3 = OpSpecConstantOp %int ISub %used_int_0 %used_int_2",
+ "%used_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_3",
+ "%used_int_5 = OpSpecConstantOp %int ISub %used_int_0 %used_int_4",
+ "%used_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_5",
+ "%used_int_7 = OpSpecConstantOp %int ISub %used_int_0 %used_int_6",
+ "%used_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_7",
+ "%used_int_9 = OpSpecConstantOp %int ISub %used_int_0 %used_int_8",
+ "%used_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_9",
+ "%used_int_11 = OpSpecConstantOp %int ISub %used_int_0 %used_int_10",
+ "%used_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_11",
+ "%used_int_13 = OpSpecConstantOp %int ISub %used_int_0 %used_int_12",
+ "%used_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_13",
+ "%used_int_15 = OpSpecConstantOp %int ISub %used_int_0 %used_int_14",
+ "%used_int_16 = OpSpecConstantOp %int ISub %used_int_0 %used_int_15",
+ "%used_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_16",
+ "%used_int_18 = OpSpecConstantOp %int ISub %used_int_0 %used_int_17",
+ "%used_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_18",
+ "%used_int_20 = OpSpecConstantOp %int ISub %used_int_0 %used_int_19",
+ "%used_vec_a = OpSpecConstantComposite %v2int %used_int_18 %used_int_19",
+ "%used_vec_b = OpSpecConstantOp %v2int IMul %used_vec_a %used_vec_a",
+ "%used_int_21 = OpSpecConstantOp %int CompositeExtract %used_vec_b 0",
+ "%used_array = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Output",
+ "%used_array_2 = OpCompositeExtract %int %used_array 2",
+ "OpStore %int_var %used_array_2",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int_1 = OpConstant %int 2",
+ "%dead_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_1",
+ "%dead_int_3 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_2",
+ "%dead_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_3",
+ "%dead_int_5 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_4",
+ "%dead_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_5",
+ "%dead_int_7 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_6",
+ "%dead_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_7",
+ "%dead_int_9 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_8",
+ "%dead_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_9",
+ "%dead_int_11 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_10",
+ "%dead_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_11",
+ "%dead_int_13 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_12",
+ "%dead_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_13",
+ "%dead_int_15 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_14",
+ "%dead_int_16 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_15",
+ "%dead_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_16",
+ "%dead_int_18 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_17",
+ "%dead_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_18",
+ "%dead_int_20 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_19",
+ "%dead_vec_a = OpSpecConstantComposite %v2int %dead_int_18 %dead_int_19",
+ "%dead_vec_b = OpSpecConstantOp %v2int IMul %dead_vec_a %dead_vec_a",
+ "%dead_int_21 = OpSpecConstantOp %int CompositeExtract %dead_vec_b 0",
+ "%dead_array = OpConstantComposite %type_arr_int_4 %dead_int_20 %used_int_20 %dead_int_19 %used_int_19",
+ },
+ /* .checks = */
+ {
+ "; CHECK: OpConstant %int 4",
+ "; CHECK: [[array:%\\w+]] = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21",
+ "; CHECK-NOT: OpConstant",
+ "; CHECK-NOT: OpSpecConstant",
+ "; CHECK: OpStore {{%\\w+}} [[array]]",
+ },
+ },
+ // Long Def-Use chain with swizzle
+ // clang-format on
+ })));
+
+TEST_F(AggressiveDCETest, DeadDecorationGroup) {
+ // The decoration group should be eliminated because the target of group
+ // decorate is dead.
+ const std::string text = R"(
+; CHECK-NOT: OpDecorat
+; CHECK-NOT: OpGroupDecorate
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %var
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr = OpTypePointer Function %uint
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr Function
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, DeadDecorationGroupAndValidDecorationMgr) {
+ // The decoration group should be eliminated because the target of group
+ // decorate is dead.
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %var
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr = OpTypePointer Function %uint
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr Function
+OpReturn
+OpFunctionEnd
+ )";
+
+ auto pass = MakeUnique<AggressiveDCEPass>();
+ auto consumer = [](spv_message_level_t, const char*, const spv_position_t&,
+ const char* message) {
+ std::cerr << message << std::endl;
+ };
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_1, consumer, text);
+
+ // Build the decoration manager before the pass.
+ context->get_decoration_mgr();
+
+ const auto status = pass->Run(context.get());
+ EXPECT_EQ(status, Pass::Status::SuccessWithChange);
+}
+
+TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroup) {
+ const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Restrict
+; CHECK: OpDecorate [[grp]] Aliased
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
+; CHECK: [[output]] = OpVariable {{%\w+}} Output
+; CHECK-NOT: OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %var %output
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr_Function = OpTypePointer Function %uint
+%uint_ptr_Output = OpTypePointer Output %uint
+%uint_0 = OpConstant %uint 0
+%output = OpVariable %uint_ptr_Output Output
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr_Function Function
+OpStore %output %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroupDifferentGroupDecorate) {
+ const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Restrict
+; CHECK: OpDecorate [[grp]] Aliased
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
+; CHECK-NOT: OpGroupDecorate
+; CHECK: [[output]] = OpVariable {{%\w+}} Output
+; CHECK-NOT: OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %output
+OpGroupDecorate %1 %var
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr_Function = OpTypePointer Function %uint
+%uint_ptr_Output = OpTypePointer Output %uint
+%uint_0 = OpConstant %uint 0
+%output = OpVariable %uint_ptr_Output Output
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr_Function Function
+OpStore %output %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, DeadGroupMemberDecorate) {
+ const std::string text = R"(
+; CHECK-NOT: OpDec
+; CHECK-NOT: OpGroup
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Offset 0
+OpDecorate %1 Uniform
+%1 = OpDecorationGroup
+OpGroupMemberDecorate %1 %var 0
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%struct = OpTypeStruct %uint %uint
+%struct_ptr = OpTypePointer Function %struct
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct_ptr Function
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, PartiallyDeadGroupMemberDecorate) {
+ const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Offset 0
+; CHECK: OpDecorate [[grp]] RelaxedPrecision
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
+; CHECK: [[output]] = OpTypeStruct
+; CHECK-NOT: OpTypeStruct
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Offset 0
+OpDecorate %1 RelaxedPrecision
+%1 = OpDecorationGroup
+OpGroupMemberDecorate %1 %var_struct 0 %output_struct 1
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%var_struct = OpTypeStruct %uint %uint
+%output_struct = OpTypeStruct %uint %uint
+%struct_ptr_Function = OpTypePointer Function %var_struct
+%struct_ptr_Output = OpTypePointer Output %output_struct
+%uint_ptr_Output = OpTypePointer Output %uint
+%output = OpVariable %struct_ptr_Output Output
+%uint_0 = OpConstant %uint 0
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct_ptr_Function Function
+%3 = OpAccessChain %uint_ptr_Output %output %uint_0
+OpStore %3 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest,
+ PartiallyDeadGroupMemberDecorateDifferentGroupDecorate) {
+ const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Offset 0
+; CHECK: OpDecorate [[grp]] RelaxedPrecision
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
+; CHECK-NOT: OpGroupMemberDecorate
+; CHECK: [[output]] = OpTypeStruct
+; CHECK-NOT: OpTypeStruct
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Offset 0
+OpDecorate %1 RelaxedPrecision
+%1 = OpDecorationGroup
+OpGroupMemberDecorate %1 %var_struct 0
+OpGroupMemberDecorate %1 %output_struct 1
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%var_struct = OpTypeStruct %uint %uint
+%output_struct = OpTypeStruct %uint %uint
+%struct_ptr_Function = OpTypePointer Function %var_struct
+%struct_ptr_Output = OpTypePointer Output %output_struct
+%uint_ptr_Output = OpTypePointer Output %uint
+%output = OpVariable %struct_ptr_Output Output
+%uint_0 = OpConstant %uint 0
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct_ptr_Function Function
+%3 = OpAccessChain %uint_ptr_Output %output %uint_0
+OpStore %3 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+// Test for #1404
+TEST_F(AggressiveDCETest, DontRemoveWorkgroupSize) {
+ const std::string text = R"(
+; CHECK: OpDecorate [[wgs:%\w+]] BuiltIn WorkgroupSize
+; CHECK: [[wgs]] = OpSpecConstantComposite
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+OpExecutionMode %func LocalSize 1 1 1
+OpDecorate %1 BuiltIn WorkgroupSize
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%functy = OpTypeFunction %void
+%v3int = OpTypeVector %int 3
+%2 = OpSpecConstant %int 1
+%1 = OpSpecConstantComposite %v3int %2 %2 %2
+%func = OpFunction %void None %functy
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<AggressiveDCEPass>(text, true);
+}
+
+// Test for #1214
+TEST_F(AggressiveDCETest, LoopHeaderIsAlsoAnotherLoopMerge) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func" %2
+OpExecutionMode %1 OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%uint_0 = OpConstant %uint 0
+%9 = OpTypeFunction %void
+%1 = OpFunction %void None %9
+%10 = OpLabel
+OpBranch %11
+%11 = OpLabel
+OpLoopMerge %12 %13 None
+OpBranchConditional %true %14 %13
+%14 = OpLabel
+OpStore %2 %uint_0
+OpLoopMerge %15 %16 None
+OpBranchConditional %true %15 %16
+%16 = OpLabel
+OpBranch %14
+%15 = OpLabel
+OpBranchConditional %true %12 %13
+%13 = OpLabel
+OpBranch %11
+%12 = OpLabel
+%17 = OpPhi %uint %uint_0 %15 %uint_0 %18
+OpStore %2 %17
+OpLoopMerge %19 %18 None
+OpBranchConditional %true %19 %18
+%18 = OpLabel
+OpBranch %12
+%19 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<AggressiveDCEPass>(text, text, true, true);
+}
+
+TEST_F(AggressiveDCETest, BreaksDontVisitPhis) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func" %var
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int = OpTypeInt 32 0
+%int_ptr_Output = OpTypePointer Output %int
+%var = OpVariable %int_ptr_Output Output
+%int0 = OpConstant %int 0
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%entry = OpLabel
+OpBranch %outer_header
+%outer_header = OpLabel
+OpLoopMerge %outer_merge %outer_continue None
+OpBranchConditional %true %inner_header %outer_continue
+%inner_header = OpLabel
+%phi = OpPhi %int %int0 %outer_header %int0 %inner_continue
+OpStore %var %phi
+OpLoopMerge %inner_merge %inner_continue None
+OpBranchConditional %true %inner_merge %inner_continue
+%inner_continue = OpLabel
+OpBranch %inner_header
+%inner_merge = OpLabel
+OpBranch %outer_continue
+%outer_continue = OpLabel
+%p = OpPhi %int %int0 %outer_header %int0 %inner_merge
+OpStore %var %p
+OpBranch %outer_header
+%outer_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange,
+ std::get<1>(SinglePassRunAndDisassemble<AggressiveDCEPass>(
+ text, false, true)));
+}
+
+// Test for #1212
+TEST_F(AggressiveDCETest, ConstStoreInnerLoop) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "main" %2
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%_ptr_Output_float = OpTypePointer Output %float
+%2 = OpVariable %_ptr_Output_float Output
+%float_3 = OpConstant %float 3
+%1 = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+OpLoopMerge %15 %16 None
+OpBranchConditional %true %17 %15
+%17 = OpLabel
+OpStore %2 %float_3
+OpLoopMerge %18 %17 None
+OpBranchConditional %true %18 %17
+%18 = OpLabel
+OpBranch %15
+%16 = OpLabel
+OpBranch %14
+%15 = OpLabel
+OpBranch %20
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(text, text, true, true);
+}
+
+// Test for #1212
+TEST_F(AggressiveDCETest, InnerLoopCopy) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "main" %2 %3
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%_ptr_Output_float = OpTypePointer Output %float
+%_ptr_Input_float = OpTypePointer Input %float
+%2 = OpVariable %_ptr_Output_float Output
+%3 = OpVariable %_ptr_Input_float Input
+%1 = OpFunction %void None %5
+%14 = OpLabel
+OpBranch %15
+%15 = OpLabel
+OpLoopMerge %16 %17 None
+OpBranchConditional %true %18 %16
+%18 = OpLabel
+%19 = OpLoad %float %3
+OpStore %2 %19
+OpLoopMerge %20 %18 None
+OpBranchConditional %true %20 %18
+%20 = OpLabel
+OpBranch %16
+%17 = OpLabel
+OpBranch %15
+%16 = OpLabel
+OpBranch %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(text, text, true, true);
+}
+
+TEST_F(AggressiveDCETest, AtomicAdd) {
+ const std::string text = R"(OpCapability SampledBuffer
+OpCapability StorageImageExtendedFormats
+OpCapability ImageBuffer
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID
+OpExecutionMode %2 LocalSize 64 1 1
+OpSource HLSL 600
+OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+OpDecorate %4 DescriptorSet 4
+OpDecorate %4 Binding 70
+%uint = OpTypeInt 32 0
+%6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui
+%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6
+%_ptr_Private_6 = OpTypePointer Private %6
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%_ptr_Image_uint = OpTypePointer Image %uint
+%4 = OpVariable %_ptr_UniformConstant_6 UniformConstant
+%16 = OpVariable %_ptr_Private_6 Private
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%2 = OpFunction %void None %10
+%17 = OpLabel
+%18 = OpLoad %6 %4
+OpStore %16 %18
+%19 = OpImageTexelPointer %_ptr_Image_uint %16 %uint_0 %uint_0
+%20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(text, text, true, true);
+}
+
+TEST_F(AggressiveDCETest, SafelyRemoveDecorateString) {
+ const std::string preamble = R"(OpCapability Shader
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+)";
+
+ const std::string body_before =
+ R"(OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "FOOBAR"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%2 = OpVariable %_ptr_StorageBuffer_uint StorageBuffer
+%1 = OpFunction %void None %4
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string body_after = R"(%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%1 = OpFunction %void None %4
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(preamble + body_before,
+ preamble + body_after, true, true);
+}
+
+TEST_F(AggressiveDCETest, CopyMemoryToGlobal) {
+ // |local| is loaded in an OpCopyMemory instruction. So the store must be
+ // kept alive.
+ const std::string test =
+ R"(OpCapability Geometry
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Geometry %main "main" %global
+OpExecutionMode %main Triangles
+OpExecutionMode %main Invocations 1
+OpExecutionMode %main OutputTriangleStrip
+OpExecutionMode %main OutputVertices 5
+OpSource GLSL 440
+OpName %main "main"
+OpName %local "local"
+OpName %global "global"
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%12 = OpConstantNull %v4float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%global = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%19 = OpLabel
+%local = OpVariable %_ptr_Function_v4float Function
+OpStore %local %12
+OpCopyMemory %global %local
+OpEndPrimitive
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(test, test, true, true);
+}
+
+TEST_F(AggressiveDCETest, CopyMemoryToLocal) {
+ // Make sure the store to |local2| using OpCopyMemory is kept and keeps
+ // |local1| alive.
+ const std::string test =
+ R"(OpCapability Geometry
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Geometry %main "main" %global
+OpExecutionMode %main Triangles
+OpExecutionMode %main Invocations 1
+OpExecutionMode %main OutputTriangleStrip
+OpExecutionMode %main OutputVertices 5
+OpSource GLSL 440
+OpName %main "main"
+OpName %local1 "local1"
+OpName %local2 "local2"
+OpName %global "global"
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%12 = OpConstantNull %v4float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%global = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%19 = OpLabel
+%local1 = OpVariable %_ptr_Function_v4float Function
+%local2 = OpVariable %_ptr_Function_v4float Function
+OpStore %local1 %12
+OpCopyMemory %local2 %local1
+OpCopyMemory %global %local2
+OpEndPrimitive
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(test, test, true, true);
+}
+
+TEST_F(AggressiveDCETest, RemoveCopyMemoryToLocal) {
+ // Test that we remove function scope variables that are stored to using
+ // OpCopyMemory, but are never loaded. We can remove both |local1| and
+ // |local2|.
+ const std::string test =
+ R"(OpCapability Geometry
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Geometry %main "main" %global
+OpExecutionMode %main Triangles
+OpExecutionMode %main Invocations 1
+OpExecutionMode %main OutputTriangleStrip
+OpExecutionMode %main OutputVertices 5
+OpSource GLSL 440
+OpName %main "main"
+OpName %local1 "local1"
+OpName %local2 "local2"
+OpName %global "global"
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%12 = OpConstantNull %v4float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%global = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%19 = OpLabel
+%local1 = OpVariable %_ptr_Function_v4float Function
+%local2 = OpVariable %_ptr_Function_v4float Function
+OpStore %local1 %12
+OpCopyMemory %local2 %local1
+OpEndPrimitive
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string result =
+ R"(OpCapability Geometry
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Geometry %main "main" %global
+OpExecutionMode %main Triangles
+OpExecutionMode %main Invocations 1
+OpExecutionMode %main OutputTriangleStrip
+OpExecutionMode %main OutputVertices 5
+OpSource GLSL 440
+OpName %main "main"
+OpName %global "global"
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%global = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%19 = OpLabel
+OpEndPrimitive
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(test, result, true, true);
+}
+
+TEST_F(AggressiveDCETest, RemoveCopyMemoryToLocal2) {
+ // We are able to remove "local2" because it is not loaded, but have to keep
+ // the stores to "local1".
+ const std::string test =
+ R"(OpCapability Geometry
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Geometry %main "main" %global
+OpExecutionMode %main Triangles
+OpExecutionMode %main Invocations 1
+OpExecutionMode %main OutputTriangleStrip
+OpExecutionMode %main OutputVertices 5
+OpSource GLSL 440
+OpName %main "main"
+OpName %local1 "local1"
+OpName %local2 "local2"
+OpName %global "global"
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%12 = OpConstantNull %v4float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%global = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%19 = OpLabel
+%local1 = OpVariable %_ptr_Function_v4float Function
+%local2 = OpVariable %_ptr_Function_v4float Function
+OpStore %local1 %12
+OpCopyMemory %local2 %local1
+OpCopyMemory %global %local1
+OpEndPrimitive
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string result =
+ R"(OpCapability Geometry
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Geometry %main "main" %global
+OpExecutionMode %main Triangles
+OpExecutionMode %main Invocations 1
+OpExecutionMode %main OutputTriangleStrip
+OpExecutionMode %main OutputVertices 5
+OpSource GLSL 440
+OpName %main "main"
+OpName %local1 "local1"
+OpName %global "global"
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%12 = OpConstantNull %v4float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%global = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %7
+%19 = OpLabel
+%local1 = OpVariable %_ptr_Function_v4float Function
+OpStore %local1 %12
+OpCopyMemory %global %local1
+OpEndPrimitive
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(test, result, true, true);
+}
+
+TEST_F(AggressiveDCETest, StructuredIfWithConditionalExit) {
+ // We are able to remove "local2" because it is not loaded, but have to keep
+ // the stores to "local1".
+ const std::string test =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpName %main "main"
+OpName %a "a"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%int_0 = OpConstant %int 0
+%bool = OpTypeBool
+%int_100 = OpConstant %int 100
+%int_1 = OpConstant %int 1
+%a = OpVariable %_ptr_Uniform_int Uniform
+%main = OpFunction %void None %5
+%12 = OpLabel
+%13 = OpLoad %int %a
+%14 = OpSGreaterThan %bool %13 %int_0
+OpSelectionMerge %15 None
+OpBranchConditional %14 %16 %15
+%16 = OpLabel
+%17 = OpLoad %int %a
+%18 = OpSLessThan %bool %17 %int_100
+OpBranchConditional %18 %19 %15
+%19 = OpLabel
+OpStore %a %int_1
+OpBranch %15
+%15 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(test, test, true, true);
+}
+
+TEST_F(AggressiveDCETest, CountingLoopNotEliminated) {
+ // #version 310 es
+ //
+ // precision highp float;
+ // precision highp int;
+ //
+ // layout(location = 0) out vec4 _GLF_color;
+ //
+ // void main()
+ // {
+ // float data[1];
+ // for (int c = 0; c < 1; c++) {
+ // if (true) {
+ // do {
+ // for (int i = 0; i < 1; i++) {
+ // data[i] = 1.0;
+ // }
+ // } while (false);
+ // }
+ // }
+ // _GLF_color = vec4(data[0], 0.0, 0.0, 1.0);
+ // }
+ const std::string test =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %_GLF_color
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %c "c"
+OpName %i "i"
+OpName %data "data"
+OpName %_GLF_color "_GLF_color"
+OpDecorate %_GLF_color Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%bool = OpTypeBool
+%float = OpTypeFloat 32
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%_ptr_Function__arr_float_uint_1 = OpTypePointer Function %_arr_float_uint_1
+%float_1 = OpConstant %float 1
+%_ptr_Function_float = OpTypePointer Function %float
+%false = OpConstantFalse %bool
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_GLF_color = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%main = OpFunction %void None %8
+%26 = OpLabel
+%c = OpVariable %_ptr_Function_int Function
+%i = OpVariable %_ptr_Function_int Function
+%data = OpVariable %_ptr_Function__arr_float_uint_1 Function
+OpStore %c %int_0
+OpBranch %27
+%27 = OpLabel
+OpLoopMerge %28 %29 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpLoad %int %c
+%32 = OpSLessThan %bool %31 %int_1
+OpBranchConditional %32 %33 %28
+%33 = OpLabel
+OpBranch %34
+%34 = OpLabel
+OpBranch %35
+%35 = OpLabel
+OpLoopMerge %36 %37 None
+OpBranch %38
+%38 = OpLabel
+OpStore %i %int_0
+OpBranch %39
+%39 = OpLabel
+OpLoopMerge %40 %41 None
+OpBranch %42
+%42 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpSLessThan %bool %43 %int_1
+OpSelectionMerge %45 None
+OpBranchConditional %44 %46 %40
+%46 = OpLabel
+%47 = OpLoad %int %i
+%48 = OpAccessChain %_ptr_Function_float %data %47
+OpStore %48 %float_1
+OpBranch %41
+%41 = OpLabel
+%49 = OpLoad %int %i
+%50 = OpIAdd %int %49 %int_1
+OpStore %i %50
+OpBranch %39
+%40 = OpLabel
+OpBranch %37
+%37 = OpLabel
+OpBranchConditional %false %35 %36
+%36 = OpLabel
+OpBranch %45
+%45 = OpLabel
+OpBranch %29
+%29 = OpLabel
+%51 = OpLoad %int %c
+%52 = OpIAdd %int %51 %int_1
+OpStore %c %52
+OpBranch %27
+%28 = OpLabel
+%53 = OpAccessChain %_ptr_Function_float %data %int_0
+%54 = OpLoad %float %53
+%55 = OpCompositeConstruct %v4float %54 %float_0 %float_0 %float_1
+OpStore %_GLF_color %55
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(test, test, true, true);
+}
+
+TEST_F(AggressiveDCETest, EliminateLoopWithUnreachable) {
+ // #version 430
+ //
+ // layout(std430) buffer U_t
+ // {
+ // float g_F[10];
+ // float g_S;
+ // };
+ //
+ // layout(location = 0)out float o;
+ //
+ // void main(void)
+ // {
+ // // Useless loop
+ // for (int i = 0; i<10; i++) {
+ // if (g_F[i] == 0.0)
+ // break;
+ // else
+ // break;
+ // // Unreachable merge block created here.
+ // // Need to edit SPIR-V to change to OpUnreachable
+ // }
+ // o = g_S;
+ // }
+
+ const std::string before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_S"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 40
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%float = OpTypeFloat 32
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10 %float
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%float_0 = OpConstant %float 0
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %9
+%23 = OpLabel
+%i = OpVariable %_ptr_Function_int Function
+OpStore %i %int_0
+OpBranch %24
+%24 = OpLabel
+OpLoopMerge %25 %26 None
+OpBranch %27
+%27 = OpLabel
+%28 = OpLoad %int %i
+%29 = OpSLessThan %bool %28 %int_10
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %31
+%33 = OpLoad %float %32
+%34 = OpFOrdEqual %bool %33 %float_0
+OpSelectionMerge %35 None
+OpBranchConditional %34 %36 %37
+%36 = OpLabel
+OpBranch %25
+%37 = OpLabel
+OpBranch %25
+%35 = OpLabel
+OpUnreachable
+%26 = OpLabel
+%38 = OpLoad %int %i
+%39 = OpIAdd %int %38 %int_1
+OpStore %i %39
+OpBranch %24
+%25 = OpLabel
+%40 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%41 = OpLoad %float %40
+OpStore %o %41
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %o
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_S"
+OpName %_ ""
+OpName %o "o"
+OpDecorate %_arr_float_uint_10 ArrayStride 4
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 40
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpDecorate %o Location 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%float = OpTypeFloat 32
+%uint = OpTypeInt 32 0
+%uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+%U_t = OpTypeStruct %_arr_float_uint_10 %float
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%o = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %9
+%23 = OpLabel
+OpBranch %24
+%24 = OpLabel
+OpBranch %25
+%25 = OpLabel
+%40 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%41 = OpLoad %float %40
+OpStore %o %41
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<AggressiveDCEPass>(before, after, true, true);
+}
+
+TEST_F(AggressiveDCETest, DeadHlslCounterBufferGOOGLE) {
+ // We are able to remove "local2" because it is not loaded, but have to keep
+ // the stores to "local1".
+ const std::string test =
+ R"(
+; CHECK-NOT: OpDecorateId
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK-NOT: OpVariable
+; CHECK: [[ac:%\w+]] = OpAccessChain {{%\w+}} [[var]]
+; CHECK: OpStore [[ac]]
+ OpCapability Shader
+ OpExtension "SPV_GOOGLE_hlsl_functionality1"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpExecutionMode %1 LocalSize 32 1 1
+ OpSource HLSL 600
+ OpDecorate %_runtimearr_v2float ArrayStride 8
+ OpMemberDecorate %_struct_3 0 Offset 0
+ OpDecorate %_struct_3 BufferBlock
+ OpMemberDecorate %_struct_4 0 Offset 0
+ OpDecorate %_struct_4 BufferBlock
+ OpDecorateId %5 HlslCounterBufferGOOGLE %6
+ OpDecorate %5 DescriptorSet 0
+ OpDecorate %5 Binding 0
+ OpDecorate %6 DescriptorSet 0
+ OpDecorate %6 Binding 1
+ %float = OpTypeFloat 32
+ %v2float = OpTypeVector %float 2
+%_runtimearr_v2float = OpTypeRuntimeArray %v2float
+ %_struct_3 = OpTypeStruct %_runtimearr_v2float
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+ %int = OpTypeInt 32 1
+ %_struct_4 = OpTypeStruct %int
+%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
+ %void = OpTypeVoid
+ %13 = OpTypeFunction %void
+ %19 = OpConstantNull %v2float
+ %int_0 = OpConstant %int 0
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+ %5 = OpVariable %_ptr_Uniform__struct_3 Uniform
+ %6 = OpVariable %_ptr_Uniform__struct_4 Uniform
+ %1 = OpFunction %void None %13
+ %22 = OpLabel
+ %23 = OpAccessChain %_ptr_Uniform_v2float %5 %int_0 %int_0
+ OpStore %23 %19
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
+}
+
+TEST_F(AggressiveDCETest, Dead) {
+ // We are able to remove "local2" because it is not loaded, but have to keep
+ // the stores to "local1".
+ const std::string test =
+ R"(
+; CHECK: OpCapability
+; CHECK-NOT: OpMemberDecorateStringGOOGLE
+; CHECK: OpFunctionEnd
+ OpCapability Shader
+ OpExtension "SPV_GOOGLE_hlsl_functionality1"
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %VSMain "VSMain"
+ OpSource HLSL 500
+ OpName %VSMain "VSMain"
+ OpName %PSInput "PSInput"
+ OpMemberName %PSInput 0 "Pos"
+ OpMemberName %PSInput 1 "uv"
+ OpMemberDecorateStringGOOGLE %PSInput 0 HlslSemanticGOOGLE "SV_POSITION"
+ OpMemberDecorateStringGOOGLE %PSInput 1 HlslSemanticGOOGLE "TEX_COORD"
+ %void = OpTypeVoid
+ %5 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%PSInput = OpTypeStruct %v4float %v2float
+ %VSMain = OpFunction %void None %5
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<AggressiveDCEPass>(test, true);
+}
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// Check that logical addressing required
+// Check that function calls inhibit optimization
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/assembly_builder.h b/test/opt/assembly_builder.h
new file mode 100644
index 0000000..1673c09
--- /dev/null
+++ b/test/opt/assembly_builder.h
@@ -0,0 +1,266 @@
+// Copyright (c) 2016 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.
+
+#ifndef TEST_OPT_ASSEMBLY_BUILDER_H_
+#define TEST_OPT_ASSEMBLY_BUILDER_H_
+
+#include <algorithm>
+#include <cstdint>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+namespace spvtools {
+namespace opt {
+
+// A simple SPIR-V assembly code builder for test uses. It builds an SPIR-V
+// assembly module from vectors of assembly strings. It allows users to add
+// instructions to the main function and the type-constants-globals section
+// directly. It relies on OpName instructions and friendly-name disassembling
+// to keep the ID names unchanged after assembling.
+//
+// An assembly module is divided into several sections, matching with the
+// SPIR-V Logical Layout:
+// Global Preamble:
+// OpCapability instructions;
+// OpExtension instructions and OpExtInstImport instructions;
+// OpMemoryModel instruction;
+// OpEntryPoint and OpExecutionMode instruction;
+// OpString, OpSourceExtension, OpSource and OpSourceContinued instructions.
+// Names:
+// OpName instructions.
+// Annotations:
+// OpDecorate, OpMemberDecorate, OpGroupDecorate, OpGroupMemberDecorate and
+// OpDecorationGroup.
+// Types, Constants and Global variables:
+// Types, constants and global variables declaration instructions.
+// Main Function:
+// Main function instructions.
+// Main Function Postamble:
+// The return and function end instructions.
+//
+// The assembly code is built by concatenating all the strings in the above
+// sections.
+//
+// Users define the contents in section <Type, Constants and Global Variables>
+// and <Main Function>. The <Names> section is to hold the names for IDs to
+// keep them unchanged before and after assembling. All defined IDs to be added
+// to this code builder will be assigned with a global name through OpName
+// instruction. The name is extracted from the definition instruction.
+// E.g. adding instruction: %var_a = OpConstant %int 2, will also add an
+// instruction: OpName %var_a, "var_a".
+//
+// Note that the name must not be used on more than one defined IDs and
+// friendly-name disassembling must be enabled so that OpName instructions will
+// be respected.
+class AssemblyBuilder {
+ // The base ID value for spec constants.
+ static const uint32_t SPEC_ID_BASE = 200;
+
+ public:
+ // Initalize a minimal SPIR-V assembly code as the template. The minimal
+ // module contains an empty main function and some predefined names for the
+ // main function.
+ AssemblyBuilder()
+ : spec_id_counter_(SPEC_ID_BASE),
+ global_preamble_({
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ // clang-format on
+ }),
+ names_(),
+ annotations_(),
+ types_consts_globals_(),
+ main_func_(),
+ main_func_postamble_({
+ "OpReturn",
+ "OpFunctionEnd",
+ }) {
+ AppendTypesConstantsGlobals({
+ "%void = OpTypeVoid",
+ "%main_func_type = OpTypeFunction %void",
+ });
+ AppendInMain({
+ "%main = OpFunction %void None %main_func_type",
+ "%main_func_entry_block = OpLabel",
+ });
+ }
+
+ // Appends OpName instructions to this builder. Instrcution strings that do
+ // not start with 'OpName ' will be skipped. Returns the references of this
+ // assembly builder.
+ AssemblyBuilder& AppendNames(const std::vector<std::string>& vec_asm_code) {
+ for (auto& inst_str : vec_asm_code) {
+ if (inst_str.find("OpName ") == 0) {
+ names_.push_back(inst_str);
+ }
+ }
+ return *this;
+ }
+
+ // Appends instructions to the types-constants-globals section and returns
+ // the reference of this assembly builder. IDs defined in the given code will
+ // be added to the Names section and then be registered with OpName
+ // instruction. Corresponding decoration instruction will be added for spec
+ // constants defined with opcode: 'OpSpecConstant'.
+ AssemblyBuilder& AppendTypesConstantsGlobals(
+ const std::vector<std::string>& vec_asm_code) {
+ AddNamesForResultIDsIn(vec_asm_code);
+ // Check spec constants defined with OpSpecConstant.
+ for (auto& inst_str : vec_asm_code) {
+ if (inst_str.find("= OpSpecConstant ") != std::string::npos ||
+ inst_str.find("= OpSpecConstantTrue ") != std::string::npos ||
+ inst_str.find("= OpSpecConstantFalse ") != std::string::npos) {
+ AddSpecIDFor(GetResultIDName(inst_str));
+ }
+ }
+ types_consts_globals_.insert(types_consts_globals_.end(),
+ vec_asm_code.begin(), vec_asm_code.end());
+ return *this;
+ }
+
+ // Appends instructions to the main function block, which is already labelled
+ // with "main_func_entry_block". Returns the reference of this assembly
+ // builder. IDs defined in the given code will be added to the Names section
+ // and then be registered with OpName instruction.
+ AssemblyBuilder& AppendInMain(const std::vector<std::string>& vec_asm_code) {
+ AddNamesForResultIDsIn(vec_asm_code);
+ main_func_.insert(main_func_.end(), vec_asm_code.begin(),
+ vec_asm_code.end());
+ return *this;
+ }
+
+ // Appends annotation instructions to the annotation section, and returns the
+ // reference of this assembly builder.
+ AssemblyBuilder& AppendAnnotations(
+ const std::vector<std::string>& vec_annotations) {
+ annotations_.insert(annotations_.end(), vec_annotations.begin(),
+ vec_annotations.end());
+ return *this;
+ }
+
+ // Pre-pends string to the preamble of the module. Useful for EFFCEE checks.
+ AssemblyBuilder& PrependPreamble(const std::vector<std::string>& preamble) {
+ preamble_.insert(preamble_.end(), preamble.begin(), preamble.end());
+ return *this;
+ }
+
+ // Get the SPIR-V assembly code as string.
+ std::string GetCode() const {
+ std::ostringstream ss;
+ for (const auto& line : preamble_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : global_preamble_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : names_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : annotations_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : types_consts_globals_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : main_func_) {
+ ss << line << std::endl;
+ }
+ for (const auto& line : main_func_postamble_) {
+ ss << line << std::endl;
+ }
+ return ss.str();
+ }
+
+ private:
+ // Adds a given name to the Name section with OpName. If the given name has
+ // been added before, does nothing.
+ void AddOpNameIfNotExist(const std::string& id_name) {
+ if (!used_names_.count(id_name)) {
+ std::stringstream opname_inst;
+ opname_inst << "OpName "
+ << "%" << id_name << " \"" << id_name << "\"";
+ names_.emplace_back(opname_inst.str());
+ used_names_.insert(id_name);
+ }
+ }
+
+ // Adds the names in a vector of assembly code strings to the Names section.
+ // If a '=' sign is found in an instruction, this instruction will be treated
+ // as an ID defining instruction. The ID name used in the instruction will be
+ // extracted and added to the Names section.
+ void AddNamesForResultIDsIn(const std::vector<std::string>& vec_asm_code) {
+ for (const auto& line : vec_asm_code) {
+ std::string name = GetResultIDName(line);
+ if (!name.empty()) {
+ AddOpNameIfNotExist(name);
+ }
+ }
+ }
+
+ // Adds an OpDecorate SpecId instruction for the given ID name.
+ void AddSpecIDFor(const std::string& id_name) {
+ std::stringstream decorate_inst;
+ decorate_inst << "OpDecorate "
+ << "%" << id_name << " SpecId " << spec_id_counter_;
+ spec_id_counter_ += 1;
+ annotations_.emplace_back(decorate_inst.str());
+ }
+
+ // Extracts the ID name from a SPIR-V assembly instruction string. If the
+ // instruction is an ID-defining instruction (has result ID), returns the
+ // name of the result ID in string. If the instruction does not have result
+ // ID, returns an empty string.
+ std::string GetResultIDName(const std::string inst_str) {
+ std::string name;
+ if (inst_str.find('=') != std::string::npos) {
+ size_t assign_sign = inst_str.find('=');
+ name = inst_str.substr(0, assign_sign);
+ name.erase(remove_if(name.begin(), name.end(),
+ [](char c) { return c == ' ' || c == '%'; }),
+ name.end());
+ }
+ return name;
+ }
+
+ uint32_t spec_id_counter_;
+ // User-defined preamble.
+ std::vector<std::string> preamble_;
+ // The vector that contains common preambles shared across all test SPIR-V
+ // code.
+ std::vector<std::string> global_preamble_;
+ // The vector that contains OpName instructions.
+ std::vector<std::string> names_;
+ // The vector that contains annotation instructions.
+ std::vector<std::string> annotations_;
+ // The vector that contains the code to declare types, constants and global
+ // variables (aka. the Types-Constants-Globals section).
+ std::vector<std::string> types_consts_globals_;
+ // The vector that contains the code in main function's entry block.
+ std::vector<std::string> main_func_;
+ // The vector that contains the postamble of main function body.
+ std::vector<std::string> main_func_postamble_;
+ // All of the defined variable names.
+ std::unordered_set<std::string> used_names_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // TEST_OPT_ASSEMBLY_BUILDER_H_
diff --git a/test/opt/assembly_builder_test.cpp b/test/opt/assembly_builder_test.cpp
new file mode 100644
index 0000000..55fbbe9
--- /dev/null
+++ b/test/opt/assembly_builder_test.cpp
@@ -0,0 +1,283 @@
+// Copyright (c) 2016 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 "test/opt/assembly_builder.h"
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using AssemblyBuilderTest = PassTest<::testing::Test>;
+
+TEST_F(AssemblyBuilderTest, MinimalShader) {
+ AssemblyBuilder builder;
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "%void = OpTypeVoid",
+ "%main_func_type = OpTypeFunction %void",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SinglePassRunAndCheck<NullPass>(builder.GetCode(), JoinAllInsts(expected),
+ /* skip_nop = */ false);
+}
+
+TEST_F(AssemblyBuilderTest, ShaderWithConstants) {
+ AssemblyBuilder builder;
+ builder
+ .AppendTypesConstantsGlobals({
+ // clang-format off
+ "%bool = OpTypeBool",
+ "%_PF_bool = OpTypePointer Function %bool",
+ "%bt = OpConstantTrue %bool",
+ "%bf = OpConstantFalse %bool",
+ "%int = OpTypeInt 32 1",
+ "%_PF_int = OpTypePointer Function %int",
+ "%si = OpConstant %int 1",
+ "%uint = OpTypeInt 32 0",
+ "%_PF_uint = OpTypePointer Function %uint",
+ "%ui = OpConstant %uint 2",
+ "%float = OpTypeFloat 32",
+ "%_PF_float = OpTypePointer Function %float",
+ "%f = OpConstant %float 3.1415",
+ "%double = OpTypeFloat 64",
+ "%_PF_double = OpTypePointer Function %double",
+ "%d = OpConstant %double 3.14159265358979",
+ // clang-format on
+ })
+ .AppendInMain({
+ // clang-format off
+ "%btv = OpVariable %_PF_bool Function",
+ "%bfv = OpVariable %_PF_bool Function",
+ "%iv = OpVariable %_PF_int Function",
+ "%uv = OpVariable %_PF_uint Function",
+ "%fv = OpVariable %_PF_float Function",
+ "%dv = OpVariable %_PF_double Function",
+ "OpStore %btv %bt",
+ "OpStore %bfv %bf",
+ "OpStore %iv %si",
+ "OpStore %uv %ui",
+ "OpStore %fv %f",
+ "OpStore %dv %d",
+ // clang-format on
+ });
+
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "OpName %bool \"bool\"",
+ "OpName %_PF_bool \"_PF_bool\"",
+ "OpName %bt \"bt\"",
+ "OpName %bf \"bf\"",
+ "OpName %int \"int\"",
+ "OpName %_PF_int \"_PF_int\"",
+ "OpName %si \"si\"",
+ "OpName %uint \"uint\"",
+ "OpName %_PF_uint \"_PF_uint\"",
+ "OpName %ui \"ui\"",
+ "OpName %float \"float\"",
+ "OpName %_PF_float \"_PF_float\"",
+ "OpName %f \"f\"",
+ "OpName %double \"double\"",
+ "OpName %_PF_double \"_PF_double\"",
+ "OpName %d \"d\"",
+ "OpName %btv \"btv\"",
+ "OpName %bfv \"bfv\"",
+ "OpName %iv \"iv\"",
+ "OpName %uv \"uv\"",
+ "OpName %fv \"fv\"",
+ "OpName %dv \"dv\"",
+ "%void = OpTypeVoid",
+"%main_func_type = OpTypeFunction %void",
+ "%bool = OpTypeBool",
+ "%_PF_bool = OpTypePointer Function %bool",
+ "%bt = OpConstantTrue %bool",
+ "%bf = OpConstantFalse %bool",
+ "%int = OpTypeInt 32 1",
+ "%_PF_int = OpTypePointer Function %int",
+ "%si = OpConstant %int 1",
+ "%uint = OpTypeInt 32 0",
+ "%_PF_uint = OpTypePointer Function %uint",
+ "%ui = OpConstant %uint 2",
+ "%float = OpTypeFloat 32",
+ "%_PF_float = OpTypePointer Function %float",
+ "%f = OpConstant %float 3.1415",
+ "%double = OpTypeFloat 64",
+ "%_PF_double = OpTypePointer Function %double",
+ "%d = OpConstant %double 3.14159265358979",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "%btv = OpVariable %_PF_bool Function",
+ "%bfv = OpVariable %_PF_bool Function",
+ "%iv = OpVariable %_PF_int Function",
+ "%uv = OpVariable %_PF_uint Function",
+ "%fv = OpVariable %_PF_float Function",
+ "%dv = OpVariable %_PF_double Function",
+ "OpStore %btv %bt",
+ "OpStore %bfv %bf",
+ "OpStore %iv %si",
+ "OpStore %uv %ui",
+ "OpStore %fv %f",
+ "OpStore %dv %d",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<NullPass>(builder.GetCode(), JoinAllInsts(expected),
+ /* skip_nop = */ false);
+}
+
+TEST_F(AssemblyBuilderTest, SpecConstants) {
+ AssemblyBuilder builder;
+ builder.AppendTypesConstantsGlobals({
+ "%bool = OpTypeBool",
+ "%uint = OpTypeInt 32 0",
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%double = OpTypeFloat 64",
+ "%v2int = OpTypeVector %int 2",
+
+ "%spec_true = OpSpecConstantTrue %bool",
+ "%spec_false = OpSpecConstantFalse %bool",
+ "%spec_uint = OpSpecConstant %uint 1",
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_float = OpSpecConstant %float 1.25",
+ "%spec_double = OpSpecConstant %double 1.2345678",
+
+ // Spec constants defined below should not have SpecID.
+ "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int",
+ "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int",
+ "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0",
+ });
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "OpName %bool \"bool\"",
+ "OpName %uint \"uint\"",
+ "OpName %int \"int\"",
+ "OpName %float \"float\"",
+ "OpName %double \"double\"",
+ "OpName %v2int \"v2int\"",
+ "OpName %spec_true \"spec_true\"",
+ "OpName %spec_false \"spec_false\"",
+ "OpName %spec_uint \"spec_uint\"",
+ "OpName %spec_int \"spec_int\"",
+ "OpName %spec_float \"spec_float\"",
+ "OpName %spec_double \"spec_double\"",
+ "OpName %spec_add_op \"spec_add_op\"",
+ "OpName %spec_vec \"spec_vec\"",
+ "OpName %spec_vec_x \"spec_vec_x\"",
+ "OpDecorate %spec_true SpecId 200",
+ "OpDecorate %spec_false SpecId 201",
+ "OpDecorate %spec_uint SpecId 202",
+ "OpDecorate %spec_int SpecId 203",
+ "OpDecorate %spec_float SpecId 204",
+ "OpDecorate %spec_double SpecId 205",
+ "%void = OpTypeVoid",
+ "%main_func_type = OpTypeFunction %void",
+ "%bool = OpTypeBool",
+ "%uint = OpTypeInt 32 0",
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%double = OpTypeFloat 64",
+ "%v2int = OpTypeVector %int 2",
+ "%spec_true = OpSpecConstantTrue %bool",
+ "%spec_false = OpSpecConstantFalse %bool",
+ "%spec_uint = OpSpecConstant %uint 1",
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_float = OpSpecConstant %float 1.25",
+ "%spec_double = OpSpecConstant %double 1.2345678",
+ "%spec_add_op = OpSpecConstantOp %int IAdd %spec_int %spec_int",
+ "%spec_vec = OpSpecConstantComposite %v2int %spec_int %spec_int",
+ "%spec_vec_x = OpSpecConstantOp %int CompositeExtract %spec_vec 0",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+
+ // clang-format on
+ };
+
+ SinglePassRunAndCheck<NullPass>(builder.GetCode(), JoinAllInsts(expected),
+ /* skip_nop = */ false);
+}
+
+TEST_F(AssemblyBuilderTest, AppendNames) {
+ AssemblyBuilder builder;
+ builder.AppendNames({
+ "OpName %void \"another_name_for_void\"",
+ "I am an invalid OpName instruction and should not be added",
+ "OpName %main \"another name for main\"",
+ });
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "OpName %void \"another_name_for_void\"",
+ "OpName %main \"another name for main\"",
+ "%void = OpTypeVoid",
+ "%main_func_type = OpTypeFunction %void",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SinglePassRunAndCheck<NullPass>(builder.GetCode(), JoinAllInsts(expected),
+ /* skip_nop = */ false);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/block_merge_test.cpp b/test/opt/block_merge_test.cpp
new file mode 100644
index 0000000..654e880
--- /dev/null
+++ b/test/opt/block_merge_test.cpp
@@ -0,0 +1,751 @@
+// 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 BlockMergeTest = PassTest<::testing::Test>;
+
+TEST_F(BlockMergeTest, Simple) {
+ // Note: SPIR-V hand edited to insert block boundary
+ // between two statements in main.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // 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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpBranch %15
+%15 = OpLabel
+%16 = OpLoad %v4float %v
+OpStore %gl_FragColor %16
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+%16 = OpLoad %v4float %v
+OpStore %gl_FragColor %16
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<BlockMergePass>(predefs + before, predefs + after, true,
+ true);
+}
+
+TEST_F(BlockMergeTest, EmptyBlock) {
+ // Note: SPIR-V hand edited to insert empty block
+ // after two statements in main.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // 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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpBranch %15
+%15 = OpLabel
+%16 = OpLoad %v4float %v
+OpStore %gl_FragColor %16
+OpBranch %17
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+%16 = OpLoad %v4float %v
+OpStore %gl_FragColor %16
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<BlockMergePass>(predefs + before, predefs + after, true,
+ true);
+}
+
+TEST_F(BlockMergeTest, NestedInControlFlow) {
+ // Note: SPIR-V hand edited to insert block boundary
+ // between OpFMul and OpStore in then-part.
+ //
+ // #version 140
+ // in vec4 BaseColor;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // bool g_B ;
+ // } ;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // if (g_B)
+ // vec4 v = v * 0.25;
+ // 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 %U_t "U_t"
+OpMemberName %U_t 0 "g_B"
+OpName %_ ""
+OpName %v_0 "v"
+OpName %gl_FragColor "gl_FragColor"
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%10 = 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
+%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
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_0_25 = OpConstant %float 0.25
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%24 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%v_0 = OpVariable %_ptr_Function_v4float Function
+%25 = OpLoad %v4float %BaseColor
+OpStore %v %25
+%26 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%27 = OpLoad %uint %26
+%28 = OpINotEqual %bool %27 %uint_0
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+%31 = OpLoad %v4float %v
+%32 = OpVectorTimesScalar %v4float %31 %float_0_25
+OpBranch %33
+%33 = OpLabel
+OpStore %v_0 %32
+OpBranch %29
+%29 = OpLabel
+%34 = OpLoad %v4float %v
+OpStore %gl_FragColor %34
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%24 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%v_0 = OpVariable %_ptr_Function_v4float Function
+%25 = OpLoad %v4float %BaseColor
+OpStore %v %25
+%26 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%27 = OpLoad %uint %26
+%28 = OpINotEqual %bool %27 %uint_0
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+%31 = OpLoad %v4float %v
+%32 = OpVectorTimesScalar %v4float %31 %float_0_25
+OpStore %v_0 %32
+OpBranch %29
+%29 = OpLabel
+%34 = OpLoad %v4float %v
+OpStore %gl_FragColor %34
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<BlockMergePass>(predefs + before, predefs + after, true,
+ true);
+}
+
+TEST_F(BlockMergeTest, PhiInSuccessorOfMergedBlock) {
+ const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[then:%\w+]] [[else:%\w+]]
+; CHECK: [[then]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[else]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpPhi {{%\w+}} %true [[then]] %false [[else]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%entry = OpLabel
+OpSelectionMerge %merge None
+OpBranchConditional %true %then %else
+%then = OpLabel
+OpBranch %then_next
+%then_next = OpLabel
+OpBranch %merge
+%else = OpLabel
+OpBranch %merge
+%merge = OpLabel
+%phi = OpPhi %bool %true %then_next %false %else
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, UpdateMergeInstruction) {
+ const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[then:%\w+]] [[else:%\w+]]
+; CHECK: [[then]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[else]] = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%entry = OpLabel
+OpSelectionMerge %real_merge None
+OpBranchConditional %true %then %else
+%then = OpLabel
+OpBranch %merge
+%else = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpBranch %real_merge
+%real_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, TwoMergeBlocksCannotBeMerged) {
+ const std::string text = R"(
+; CHECK: OpSelectionMerge [[outer_merge:%\w+]] None
+; CHECK: OpSelectionMerge [[inner_merge:%\w+]] None
+; CHECK: [[inner_merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[outer_merge]]
+; CHECK: [[outer_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%entry = OpLabel
+OpSelectionMerge %outer_merge None
+OpBranchConditional %true %then %else
+%then = OpLabel
+OpBranch %inner_header
+%else = OpLabel
+OpBranch %inner_header
+%inner_header = OpLabel
+OpSelectionMerge %inner_merge None
+OpBranchConditional %true %inner_then %inner_else
+%inner_then = OpLabel
+OpBranch %inner_merge
+%inner_else = OpLabel
+OpBranch %inner_merge
+%inner_merge = OpLabel
+OpBranch %outer_merge
+%outer_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, MergeContinue) {
+ const std::string text = R"(
+; CHECK: OpBranch [[header:%\w+]]
+; CHECK: [[header]] = OpLabel
+; CHECK-NEXT: OpLogicalAnd
+; CHECK-NEXT: OpLoopMerge {{%\w+}} [[header]] None
+; CHECK-NEXT: OpBranch [[header]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%entry = OpLabel
+OpBranch %header
+%header = OpLabel
+OpLoopMerge %merge %continue None
+OpBranch %continue
+%continue = OpLabel
+%op = OpLogicalAnd %bool %true %false
+OpBranch %header
+%merge = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, TwoHeadersCannotBeMerged) {
+ const std::string text = R"(
+; CHECK: OpBranch [[loop_header:%\w+]]
+; CHECK: [[loop_header]] = OpLabel
+; CHECK-NEXT: OpLoopMerge
+; CHECK-NEXT: OpBranch [[if_header:%\w+]]
+; CHECK: [[if_header]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%entry = OpLabel
+OpBranch %header
+%header = OpLabel
+OpLoopMerge %merge %continue None
+OpBranch %inner_header
+%inner_header = OpLabel
+OpSelectionMerge %continue None
+OpBranchConditional %true %then %continue
+%then = OpLabel
+OpBranch %continue
+%continue = OpLabel
+OpBranchConditional %false %merge %header
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, RemoveStructuredDeclaration) {
+ // Note: SPIR-V hand edited remove dead branch and add block
+ // before continue block
+ //
+ // #version 140
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // while (true) {
+ // break;
+ // }
+ // gl_FragColor = BaseColor;
+ // }
+
+ const std::string assembly =
+ R"(
+; CHECK: OpLabel
+; CHECK: [[header:%\w+]] = OpLabel
+; CHECK-NOT: OpLoopMerge
+; CHECK: OpReturn
+; CHECK: [[continue:%\w+]] = OpLabel
+; CHECK-NEXT: OpBranch [[header]]
+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 %gl_FragColor "gl_FragColor"
+OpName %BaseColor "BaseColor"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_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
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+OpLoopMerge %15 %16 None
+OpBranch %17
+%17 = OpLabel
+OpBranch %15
+%18 = OpLabel
+OpBranch %16
+%16 = OpLabel
+OpBranch %14
+%15 = OpLabel
+%19 = OpLoad %v4float %BaseColor
+OpStore %gl_FragColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(assembly, true);
+}
+
+TEST_F(BlockMergeTest, DontMergeKill) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[ret:%\w+]]
+; CHECK: [[ret:%\w+]] = OpLabel
+; CHECK-NEXT: OpKill
+; CHECK-DAG: [[cont]] = OpLabel
+; CHECK-DAG: [[merge]] = OpLabel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %5
+%5 = OpLabel
+OpKill
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, DontMergeUnreachable) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[ret:%\w+]]
+; CHECK: [[ret:%\w+]] = OpLabel
+; CHECK-NEXT: OpUnreachable
+; CHECK-DAG: [[cont]] = OpLabel
+; CHECK-DAG: [[merge]] = OpLabel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %5
+%5 = OpLabel
+OpUnreachable
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, DontMergeReturn) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[ret:%\w+]]
+; CHECK: [[ret:%\w+]] = OpLabel
+; CHECK-NEXT: OpReturn
+; CHECK-DAG: [[cont]] = OpLabel
+; CHECK-DAG: [[merge]] = OpLabel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %5
+%5 = OpLabel
+OpReturn
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, DontMergeSwitch) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[ret:%\w+]]
+; CHECK: [[ret:%\w+]] = OpLabel
+; CHECK-NEXT: OpSwitch
+; CHECK-DAG: [[cont]] = OpLabel
+; CHECK-DAG: [[merge]] = OpLabel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %5
+%5 = OpLabel
+OpSwitch %int_0 %6
+%6 = OpLabel
+OpReturn
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, DontMergeReturnValue) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] [[cont:%\w+]] None
+; CHECK-NEXT: OpBranch [[ret:%\w+]]
+; CHECK: [[ret:%\w+]] = OpLabel
+; CHECK-NEXT: OpReturn
+; CHECK-DAG: [[cont]] = OpLabel
+; CHECK-DAG: [[merge]] = OpLabel
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%functy = OpTypeFunction %void
+%otherfuncty = OpTypeFunction %bool
+%true = OpConstantTrue %bool
+%func = OpFunction %void None %functy
+%1 = OpLabel
+%2 = OpFunctionCall %bool %3
+OpReturn
+OpFunctionEnd
+%3 = OpFunction %bool None %otherfuncty
+%4 = OpLabel
+OpBranch %5
+%5 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpReturnValue %true
+%7 = OpLabel
+OpBranch %5
+%6 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+TEST_F(BlockMergeTest, MergeHeaders) {
+ // Merge two headers when the second is the merge block of the first.
+ const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpBranch [[header:%\w+]]
+; CHECK-NEXT: [[header]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHEKC: OpReturn
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%functy = OpTypeFunction %void
+%otherfuncty = OpTypeFunction %bool
+%true = OpConstantTrue %bool
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %5
+%5 = OpLabel
+OpLoopMerge %8 %7 None
+OpBranch %8
+%7 = OpLabel
+OpBranch %5
+%8 = OpLabel
+OpSelectionMerge %m None
+OpBranchConditional %true %a %m
+%a = OpLabel
+OpBranch %m
+%m = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<BlockMergePass>(text, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// More complex control flow
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/ccp_test.cpp b/test/opt/ccp_test.cpp
new file mode 100644
index 0000000..20d883b
--- /dev/null
+++ b/test/opt/ccp_test.cpp
@@ -0,0 +1,901 @@
+// 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 "gtest/gtest.h"
+#include "source/opt/ccp_pass.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CCPTest = PassTest<::testing::Test>;
+
+TEST_F(CCPTest, PropagateThroughPhis) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %x %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %x "x"
+ OpName %outparm "outparm"
+ OpDecorate %x Flat
+ OpDecorate %x Location 0
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_4 = OpConstant %int 4
+ %int_3 = OpConstant %int 3
+ %int_1 = OpConstant %int 1
+%_ptr_Input_int = OpTypePointer Input %int
+ %x = OpVariable %_ptr_Input_int Input
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %3
+ %4 = OpLabel
+ %5 = OpLoad %int %x
+ %9 = OpIAdd %int %int_1 %int_3
+ %6 = OpSGreaterThan %bool %5 %int_3
+ OpSelectionMerge %25 None
+ OpBranchConditional %6 %22 %23
+ %22 = OpLabel
+
+; CHECK: OpCopyObject %int %int_4
+ %7 = OpCopyObject %int %9
+
+ OpBranch %25
+ %23 = OpLabel
+ %8 = OpCopyObject %int %int_4
+ OpBranch %25
+ %25 = OpLabel
+
+; %int_4 should have propagated to both OpPhi operands.
+; CHECK: OpPhi %int %int_4 {{%\d+}} %int_4 {{%\d+}}
+ %35 = OpPhi %int %7 %22 %8 %23
+
+; This function always returns 4. DCE should get rid of everything else.
+; CHECK OpStore %outparm %int_4
+ OpStore %outparm %35
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, SimplifyConditionals) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %outparm "outparm"
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_4 = OpConstant %int 4
+ %int_3 = OpConstant %int 3
+ %int_1 = OpConstant %int 1
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %3
+ %4 = OpLabel
+ %9 = OpIAdd %int %int_4 %int_3
+ %6 = OpSGreaterThan %bool %9 %int_3
+ OpSelectionMerge %25 None
+; CHECK: OpBranchConditional %true [[bb_taken:%\d+]] [[bb_not_taken:%\d+]]
+ OpBranchConditional %6 %22 %23
+; CHECK: [[bb_taken]] = OpLabel
+ %22 = OpLabel
+; CHECK: OpCopyObject %int %int_7
+ %7 = OpCopyObject %int %9
+ OpBranch %25
+; CHECK: [[bb_not_taken]] = OpLabel
+ %23 = OpLabel
+; CHECK: [[id_not_evaluated:%\d+]] = OpCopyObject %int %int_4
+ %8 = OpCopyObject %int %int_4
+ OpBranch %25
+ %25 = OpLabel
+
+; %int_7 should have propagated to the first OpPhi operand. But the else branch
+; is not executable (conditional is always true), so no values should be
+; propagated there and the value of the OpPhi should always be %int_7.
+; CHECK: OpPhi %int %int_7 [[bb_taken]] [[id_not_evaluated]] [[bb_not_taken]]
+ %35 = OpPhi %int %7 %22 %8 %23
+
+; Only the true path of the conditional is ever executed. The output of this
+; function is always %int_7.
+; CHECK: OpStore %outparm %int_7
+ OpStore %outparm %35
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, SimplifySwitches) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %outparm "outparm"
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_23 = OpConstant %int 23
+ %int_42 = OpConstant %int 42
+ %int_14 = OpConstant %int 14
+ %int_15 = OpConstant %int 15
+ %int_4 = OpConstant %int 4
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %6
+ %15 = OpLabel
+ OpSelectionMerge %17 None
+ OpSwitch %int_23 %17 10 %18 13 %19 23 %20
+ %18 = OpLabel
+ OpBranch %17
+ %19 = OpLabel
+ OpBranch %17
+ %20 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ %24 = OpPhi %int %int_23 %15 %int_42 %18 %int_14 %19 %int_15 %20
+
+; The switch will always jump to label %20, which carries the value %int_15.
+; CHECK: OpIAdd %int %int_15 %int_4
+ %22 = OpIAdd %int %24 %int_4
+
+; Consequently, the return value will always be %int_19.
+; CHECK: OpStore %outparm %int_19
+ OpStore %outparm %22
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, SimplifySwitchesDefaultBranch) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %outparm "outparm"
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_42 = OpConstant %int 42
+ %int_4 = OpConstant %int 4
+ %int_1 = OpConstant %int 1
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %6
+ %13 = OpLabel
+ %15 = OpIAdd %int %int_42 %int_4
+ OpSelectionMerge %16 None
+
+; CHECK: OpSwitch %int_46 {{%\d+}} 10 {{%\d+}}
+ OpSwitch %15 %17 10 %18
+ %18 = OpLabel
+ OpBranch %16
+ %17 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %22 = OpPhi %int %int_42 %18 %int_1 %17
+
+; The switch will always jump to the default label %17. This carries the value
+; %int_1.
+; CHECK: OpIAdd %int %int_1 %int_4
+ %20 = OpIAdd %int %22 %int_4
+
+; Resulting in a return value of %int_5.
+; CHECK: OpStore %outparm %int_5
+ OpStore %outparm %20
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, SimplifyIntVector) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %OutColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %v "v"
+ OpName %OutColor "OutColor"
+ OpDecorate %OutColor Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v4int = OpTypeVector %int 4
+%_ptr_Function_v4int = OpTypePointer Function %v4int
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %int_3 = OpConstant %int 3
+ %int_4 = OpConstant %int 4
+ %14 = OpConstantComposite %v4int %int_1 %int_2 %int_3 %int_4
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Output_v4int = OpTypePointer Output %v4int
+ %OutColor = OpVariable %_ptr_Output_v4int Output
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %v = OpVariable %_ptr_Function_v4int Function
+ OpStore %v %14
+ %18 = OpAccessChain %_ptr_Function_int %v %uint_0
+ %19 = OpLoad %int %18
+
+; The constant folder does not see through access chains. To get this, the
+; vector would have to be scalarized.
+; CHECK: [[result_id:%\d+]] = OpIAdd %int {{%\d+}} %int_1
+ %20 = OpIAdd %int %19 %int_1
+ %21 = OpAccessChain %_ptr_Function_int %v %uint_0
+
+; CHECK: OpStore {{%\d+}} [[result_id]]
+ OpStore %21 %20
+ %24 = OpLoad %v4int %v
+ OpStore %OutColor %24
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, BadSimplifyFloatVector) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %OutColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %v "v"
+ OpName %OutColor "OutColor"
+ OpDecorate %OutColor Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %float_1 = OpConstant %float 1
+ %float_2 = OpConstant %float 2
+ %float_3 = OpConstant %float 3
+ %float_4 = OpConstant %float 4
+ %14 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %OutColor = OpVariable %_ptr_Output_v4float Output
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %v = OpVariable %_ptr_Function_v4float Function
+ OpStore %v %14
+ %18 = OpAccessChain %_ptr_Function_float %v %uint_0
+ %19 = OpLoad %float %18
+
+; NOTE: This test should start failing once floating point folding is
+; implemented (https://github.com/KhronosGroup/SPIRV-Tools/issues/943).
+; This should be checking that we are adding %float_1 + %float_1.
+; CHECK: [[result_id:%\d+]] = OpFAdd %float {{%\d+}} %float_1
+ %20 = OpFAdd %float %19 %float_1
+ %21 = OpAccessChain %_ptr_Function_float %v %uint_0
+
+; This should be checkint that we are storing %float_2 instead of result_it.
+; CHECK: OpStore {{%\d+}} [[result_id]]
+ OpStore %21 %20
+ %24 = OpLoad %v4float %v
+ OpStore %OutColor %24
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, NoLoadStorePropagation) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %x "x"
+ OpName %outparm "outparm"
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_23 = OpConstant %int 23
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %x = OpVariable %_ptr_Function_int Function
+ OpStore %x %int_23
+
+; int_23 should not propagate into this load.
+; CHECK: [[load_id:%\d+]] = OpLoad %int %x
+ %12 = OpLoad %int %x
+
+; Nor into this copy operation.
+; CHECK: [[copy_id:%\d+]] = OpCopyObject %int [[load_id]]
+ %13 = OpCopyObject %int %12
+
+; Likewise here.
+; CHECK: OpStore %outparm [[copy_id]]
+ OpStore %outparm %13
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, HandleAbortInstructions) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 500
+ OpName %main "main"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %bool = OpTypeBool
+; CHECK: %true = OpConstantTrue %bool
+ %int_3 = OpConstant %int 3
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %4 = OpLabel
+ %9 = OpIAdd %int %int_3 %int_1
+ %6 = OpSGreaterThan %bool %9 %int_3
+ OpSelectionMerge %23 None
+; CHECK: OpBranchConditional %true {{%\d+}} {{%\d+}}
+ OpBranchConditional %6 %22 %23
+ %22 = OpLabel
+ OpKill
+ %23 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, SSAWebCycles) {
+ // Test reduced from https://github.com/KhronosGroup/SPIRV-Tools/issues/1159
+ // When there is a cycle in the SSA def-use web, the propagator was getting
+ // into an infinite loop. SSA edges for Phi instructions should not be
+ // added to the edges to simulate.
+ const std::string spv_asm = 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
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_4 = OpConstant %int 4
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+%_ptr_Output_int = OpTypePointer Output %int
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ %29 = OpPhi %int %int_0 %5 %22 %14
+ %30 = OpPhi %int %int_0 %5 %25 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %bool %30 %int_4
+; CHECK: OpBranchConditional %true {{%\d+}} {{%\d+}}
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+; CHECK: OpIAdd %int %int_0 %int_0
+ %22 = OpIAdd %int %29 %30
+ OpBranch %14
+ %14 = OpLabel
+; CHECK: OpPhi %int %int_0 {{%\d+}}
+ %25 = OpPhi %int %30 %12
+ OpBranch %11
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, LoopInductionVariables) {
+ // Test reduced from https://github.com/KhronosGroup/SPIRV-Tools/issues/1143
+ // We are failing to properly consider the induction variable for this loop
+ // as Varying.
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 430
+ OpName %main "main"
+ %void = OpTypeVoid
+ %5 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %5
+ %12 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+
+; This Phi should not have all constant arguments:
+; CHECK: [[phi_id:%\d+]] = OpPhi %int %int_0 {{%\d+}} {{%\d+}} {{%\d+}}
+ %22 = OpPhi %int %int_0 %12 %21 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+
+; The Phi should never be considered to have the value %int_0.
+; CHECK: [[branch_selector:%\d+]] = OpSLessThan %bool [[phi_id]] %int_10
+ %18 = OpSLessThan %bool %22 %int_10
+
+; This conditional was wrongly converted into an always-true jump due to the
+; bad meet evaluation of %22.
+; CHECK: OpBranchConditional [[branch_selector]] {{%\d+}} {{%\d+}}
+ OpBranchConditional %18 %19 %14
+ %19 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+; CHECK: OpIAdd %int [[phi_id]] %int_1
+ %21 = OpIAdd %int %22 %int_1
+ OpBranch %13
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<CCPPass>(spv_asm, true);
+}
+
+TEST_F(CCPTest, HandleCompositeWithUndef) {
+ // Check to make sure that CCP does not crash when given a "constant" struct
+ // with an undef. If at a later time CCP is enhanced to optimize this case,
+ // it is not wrong.
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 500
+ OpName %main "main"
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %bool = OpTypeBool
+ %_struct_7 = OpTypeStruct %int %int
+ %int_1 = OpConstant %int 1
+ %9 = OpUndef %int
+ %10 = OpConstantComposite %_struct_7 %int_1 %9
+ %main = OpFunction %void None %4
+ %11 = OpLabel
+ %12 = OpCompositeExtract %int %10 0
+ %13 = OpCopyObject %int %12
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto res = SinglePassRunToBinary<CCPPass>(spv_asm, true);
+ EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(CCPTest, SkipSpecConstantInstrucitons) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 500
+ OpName %main "main"
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %bool = OpTypeBool
+ %10 = OpSpecConstantFalse %bool
+ %main = OpFunction %void None %4
+ %11 = OpLabel
+ %12 = OpBranchConditional %10 %l1 %l2
+ %l1 = OpLabel
+ OpReturn
+ %l2 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto res = SinglePassRunToBinary<CCPPass>(spv_asm, true);
+ EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(CCPTest, UpdateSubsequentPhisToVarying) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func" %in
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 1
+%false = OpConstantFalse %bool
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%int6 = OpConstant %int 6
+%int_ptr_Input = OpTypePointer Input %int
+%in = OpVariable %int_ptr_Input Input
+%undef = OpUndef %int
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%outer_phi = OpPhi %int %int0 %1 %outer_add %15
+%cond1 = OpSLessThanEqual %bool %outer_phi %int6
+OpLoopMerge %3 %15 None
+OpBranchConditional %cond1 %4 %3
+%4 = OpLabel
+%ld = OpLoad %int %in
+%cond2 = OpSGreaterThanEqual %bool %int1 %ld
+OpSelectionMerge %10 None
+OpBranchConditional %cond2 %8 %9
+%8 = OpLabel
+OpBranch %10
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+%extra_phi = OpPhi %int %outer_phi %8 %outer_phi %9
+OpBranch %11
+%11 = OpLabel
+%inner_phi = OpPhi %int %int0 %10 %inner_add %13
+%cond3 = OpSLessThanEqual %bool %inner_phi %int6
+OpLoopMerge %14 %13 None
+OpBranchConditional %cond3 %12 %14
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+%inner_add = OpIAdd %int %inner_phi %int1
+OpBranch %11
+%14 = OpLabel
+OpBranch %15
+%15 = OpLabel
+%outer_add = OpIAdd %int %extra_phi %int1
+OpBranch %2
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ auto res = SinglePassRunToBinary<CCPPass>(text, true);
+ EXPECT_EQ(std::get<1>(res), Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(CCPTest, UndefInPhi) {
+ const std::string text = R"(
+; CHECK: [[uint1:%\w+]] = OpConstant {{%\w+}} 1
+; CHECK: [[phi:%\w+]] = OpPhi
+; CHECK: OpIAdd {{%\w+}} [[phi]] [[uint1]]
+ OpCapability Kernel
+ OpCapability Linkage
+ OpMemoryModel Logical OpenCL
+ OpDecorate %1 LinkageAttributes "func" Export
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+ %7 = OpUndef %uint
+ %8 = OpTypeFunction %void %bool
+ %1 = OpFunction %void None %8
+ %9 = OpFunctionParameter %bool
+ %10 = OpLabel
+ OpBranchConditional %9 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %12 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ OpBranchConditional %9 %13 %15
+ %15 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %16 = OpPhi %uint %uint_0 %11 %7 %14 %uint_1 %15
+ %17 = OpIAdd %uint %16 %uint_1
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
+// Just test to make sure the constant fold rules are being used. Will rely on
+// the folding test for specific testing of specific rules.
+TEST_F(CCPTest, UseConstantFoldingRules) {
+ const std::string text = R"(
+; CHECK: [[float1:%\w+]] = OpConstant {{%\w+}} 1
+; CHECK: OpReturnValue [[float1]]
+ OpCapability Shader
+ OpCapability Linkage
+ OpMemoryModel Logical GLSL450
+ OpDecorate %1 LinkageAttributes "func" Export
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %float = OpTypeFloat 32
+ %float_0 = OpConstant %float 0
+ %float_1 = OpConstant %float 1
+ %8 = OpTypeFunction %float
+ %1 = OpFunction %float None %8
+ %10 = OpLabel
+ %17 = OpFAdd %float %float_0 %float_1
+ OpReturnValue %17
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
+// Test for #1300. Previously value for %5 would not settle during simulation.
+TEST_F(CCPTest, SettlePhiLatticeValue) {
+ const std::string text = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+OpDecorate %func LinkageAttributes "func" Export
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranchConditional %true %2 %3
+%3 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%5 = OpPhi %bool %true %1 %false %3
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunToBinary<CCPPass>(text, true);
+}
+
+TEST_F(CCPTest, NullBranchCondition) {
+ const std::string text = R"(
+; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1
+; CHECK: [[int2:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpIAdd {{%\w+}} [[int1]] [[int2]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 1
+%null = OpConstantNull %bool
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpSelectionMerge %2 None
+OpBranchConditional %null %2 %3
+%3 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%phi = OpPhi %int %int_1 %1 %int_2 %3
+%add = OpIAdd %int %int_1 %phi
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
+TEST_F(CCPTest, UndefBranchCondition) {
+ const std::string text = R"(
+; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1
+; CHECK: [[phi:%\w+]] = OpPhi
+; CHECK: OpIAdd {{%\w+}} [[int1]] [[phi]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 1
+%undef = OpUndef %bool
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpSelectionMerge %2 None
+OpBranchConditional %undef %2 %3
+%3 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%phi = OpPhi %int %int_1 %1 %int_2 %3
+%add = OpIAdd %int %int_1 %phi
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
+TEST_F(CCPTest, NullSwitchCondition) {
+ const std::string text = R"(
+; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1
+; CHECK: [[int2:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpIAdd {{%\w+}} [[int1]] [[int2]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%int = OpTypeInt 32 1
+%null = OpConstantNull %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpSelectionMerge %2 None
+OpSwitch %null %2 0 %3
+%3 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%phi = OpPhi %int %int_1 %1 %int_2 %3
+%add = OpIAdd %int %int_1 %phi
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
+TEST_F(CCPTest, UndefSwitchCondition) {
+ const std::string text = R"(
+; CHECK: [[int1:%\w+]] = OpConstant {{%\w+}} 1
+; CHECK: [[phi:%\w+]] = OpPhi
+; CHECK: OpIAdd {{%\w+}} [[int1]] [[phi]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%int = OpTypeInt 32 1
+%undef = OpUndef %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpSelectionMerge %2 None
+OpSwitch %undef %2 0 %3
+%3 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%phi = OpPhi %int %int_1 %1 %int_2 %3
+%add = OpIAdd %int %int_1 %phi
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
+// Test for #1361.
+TEST_F(CCPTest, CompositeConstructOfGlobalValue) {
+ const std::string text = R"(
+; CHECK: [[phi:%\w+]] = OpPhi
+; CHECK-NEXT: OpCompositeExtract {{%\w+}} [[phi]] 0
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func" %in
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%int = OpTypeInt 32 1
+%bool = OpTypeBool
+%functy = OpTypeFunction %void
+%ptr_int_Input = OpTypePointer Input %int
+%in = OpVariable %ptr_int_Input Input
+%struct = OpTypeStruct %ptr_int_Input %ptr_int_Input
+%struct_null = OpConstantNull %struct
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%phi = OpPhi %struct %struct_null %1 %5 %4
+%extract = OpCompositeExtract %ptr_int_Input %phi 0
+OpLoopMerge %3 %4 None
+OpBranch %4
+%4 = OpLabel
+%5 = OpCompositeConstruct %struct %in %in
+OpBranch %2
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CCPPass>(text, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/cfg_cleanup_test.cpp b/test/opt/cfg_cleanup_test.cpp
new file mode 100644
index 0000000..3498f00
--- /dev/null
+++ b/test/opt/cfg_cleanup_test.cpp
@@ -0,0 +1,456 @@
+// 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 "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CFGCleanupTest = PassTest<::testing::Test>;
+
+TEST_F(CFGCleanupTest, RemoveUnreachableBlocks) {
+ const std::string declarations = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %inf %outf4
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %inf "inf"
+OpName %outf4 "outf4"
+OpDecorate %inf Location 0
+OpDecorate %outf4 Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+%inf = OpVariable %_ptr_Input_float Input
+%float_2 = OpConstant %float 2
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outf4 = OpVariable %_ptr_Output_v4float Output
+%float_n0_5 = OpConstant %float -0.5
+)";
+
+ const std::string body_before = R"(%main = OpFunction %void None %6
+%14 = OpLabel
+OpBranch %18
+%19 = OpLabel
+%20 = OpLoad %float %inf
+%21 = OpCompositeConstruct %v4float %20 %20 %20 %20
+OpStore %outf4 %21
+OpBranch %17
+%18 = OpLabel
+%22 = OpLoad %float %inf
+%23 = OpFAdd %float %22 %float_n0_5
+%24 = OpCompositeConstruct %v4float %23 %23 %23 %23
+OpStore %outf4 %24
+OpBranch %17
+%17 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string body_after = R"(%main = OpFunction %void None %6
+%14 = OpLabel
+OpBranch %15
+%15 = OpLabel
+%20 = OpLoad %float %inf
+%21 = OpFAdd %float %20 %float_n0_5
+%22 = OpCompositeConstruct %v4float %21 %21 %21 %21
+OpStore %outf4 %22
+OpBranch %19
+%19 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CFGCleanupPass>(declarations + body_before,
+ declarations + body_after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemoveDecorations) {
+ const std::string before = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ OpName %main "main"
+ OpName %x "x"
+ OpName %dead "dead"
+ OpDecorate %x RelaxedPrecision
+ OpDecorate %dead RelaxedPrecision
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+ %float_2 = OpConstant %float 2
+ %float_4 = OpConstant %float 4
+
+ %main = OpFunction %void None %6
+ %14 = OpLabel
+ %x = OpVariable %_ptr_Function_float Function
+ OpBranch %18
+ %19 = OpLabel
+ %dead = OpVariable %_ptr_Function_float Function
+ OpStore %dead %float_2
+ OpBranch %17
+ %18 = OpLabel
+ OpStore %x %float_4
+ OpBranch %17
+ %17 = 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
+OpName %main "main"
+OpName %x "x"
+OpDecorate %x RelaxedPrecision
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_2 = OpConstant %float 2
+%float_4 = OpConstant %float 4
+%main = OpFunction %void None %6
+%11 = OpLabel
+%x = OpVariable %_ptr_Function_float Function
+OpBranch %12
+%12 = OpLabel
+OpStore %x %float_4
+OpBranch %14
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CFGCleanupPass>(before, after, true, true);
+}
+
+TEST_F(CFGCleanupTest, UpdatePhis) {
+ const std::string before = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %y %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpName %main "main"
+ OpName %y "y"
+ OpName %outparm "outparm"
+ OpDecorate %y Flat
+ OpDecorate %y Location 0
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+ %y = OpVariable %_ptr_Input_int Input
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_42 = OpConstant %int 42
+ %int_23 = OpConstant %int 23
+ %int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %11 = OpLoad %int %y
+ OpBranch %21
+ %16 = OpLabel
+ %20 = OpIAdd %int %11 %int_42
+ OpBranch %17
+ %21 = OpLabel
+ %24 = OpISub %int %11 %int_23
+ OpBranch %17
+ %17 = OpLabel
+ %31 = OpPhi %int %20 %16 %24 %21
+ %27 = OpIAdd %int %31 %int_5
+ OpStore %outparm %27
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %y %outparm
+OpExecutionMode %main OriginUpperLeft
+OpName %main "main"
+OpName %y "y"
+OpName %outparm "outparm"
+OpDecorate %y Flat
+OpDecorate %y Location 0
+OpDecorate %outparm Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+%y = OpVariable %_ptr_Input_int Input
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_42 = OpConstant %int 42
+%int_23 = OpConstant %int 23
+%int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+%outparm = OpVariable %_ptr_Output_int Output
+%main = OpFunction %void None %6
+%16 = OpLabel
+%17 = OpLoad %int %y
+OpBranch %18
+%18 = OpLabel
+%22 = OpISub %int %17 %int_23
+OpBranch %21
+%21 = OpLabel
+%23 = OpPhi %int %22 %18
+%24 = OpIAdd %int %23 %int_5
+OpStore %outparm %24
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CFGCleanupPass>(before, after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemoveNamedLabels) {
+ const std::string before = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpSource GLSL 430
+ OpName %main "main"
+ OpName %dead "dead"
+ %void = OpTypeVoid
+ %5 = OpTypeFunction %void
+ %main = OpFunction %void None %5
+ %6 = OpLabel
+ OpReturn
+ %dead = OpLabel
+ OpReturn
+ OpFunctionEnd)";
+
+ const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 430
+OpName %main "main"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%main = OpFunction %void None %5
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CFGCleanupPass>(before, after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemovePhiArgsFromFarBlocks) {
+ const std::string before = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %y %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpName %main "main"
+ OpName %y "y"
+ OpName %outparm "outparm"
+ OpDecorate %y Flat
+ OpDecorate %y Location 0
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+ %y = OpVariable %_ptr_Input_int Input
+ %int_42 = OpConstant %int 42
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %int_14 = OpConstant %int 14
+ %int_15 = OpConstant %int 15
+ %int_5 = OpConstant %int 5
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %40
+ %41 = OpLabel
+ %11 = OpLoad %int %y
+ OpBranch %40
+ %40 = OpLabel
+ %12 = OpLoad %int %y
+ OpSelectionMerge %16 None
+ OpSwitch %12 %16 10 %13 13 %14 18 %15
+ %13 = OpLabel
+ OpBranch %16
+ %14 = OpLabel
+ OpStore %outparm %int_14
+ OpBranch %16
+ %15 = OpLabel
+ OpStore %outparm %int_15
+ OpBranch %16
+ %16 = OpLabel
+ %30 = OpPhi %int %11 %40 %int_42 %13 %11 %14 %11 %15
+ %28 = OpIAdd %int %30 %int_5
+ OpStore %outparm %28
+ OpReturn
+ OpFunctionEnd)";
+
+ const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %y %outparm
+OpExecutionMode %main OriginUpperLeft
+OpName %main "main"
+OpName %y "y"
+OpName %outparm "outparm"
+OpDecorate %y Flat
+OpDecorate %y Location 0
+OpDecorate %outparm Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+%y = OpVariable %_ptr_Input_int Input
+%int_42 = OpConstant %int 42
+%_ptr_Output_int = OpTypePointer Output %int
+%outparm = OpVariable %_ptr_Output_int Output
+%int_14 = OpConstant %int 14
+%int_15 = OpConstant %int 15
+%int_5 = OpConstant %int 5
+%26 = OpUndef %int
+%main = OpFunction %void None %6
+%15 = OpLabel
+OpBranch %16
+%16 = OpLabel
+%19 = OpLoad %int %y
+OpSelectionMerge %20 None
+OpSwitch %19 %20 10 %21 13 %22 18 %23
+%21 = OpLabel
+OpBranch %20
+%22 = OpLabel
+OpStore %outparm %int_14
+OpBranch %20
+%23 = OpLabel
+OpStore %outparm %int_15
+OpBranch %20
+%20 = OpLabel
+%24 = OpPhi %int %26 %16 %int_42 %21 %26 %22 %26 %23
+%25 = OpIAdd %int %24 %int_5
+OpStore %outparm %25
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CFGCleanupPass>(before, after, true, true);
+}
+
+TEST_F(CFGCleanupTest, RemovePhiConstantArgs) {
+ const std::string before = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %y %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpName %main "main"
+ OpName %y "y"
+ OpName %outparm "outparm"
+ OpDecorate %y Flat
+ OpDecorate %y Location 0
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+ %y = OpVariable %_ptr_Input_int Input
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_23 = OpConstant %int 23
+ %int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %24 = OpUndef %int
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %14
+ %40 = OpLabel
+ %9 = OpLoad %int %y
+ %12 = OpSGreaterThan %bool %9 %int_10
+ OpSelectionMerge %14 None
+ OpBranchConditional %12 %13 %14
+ %13 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ %25 = OpPhi %int %24 %5 %int_23 %13
+ %20 = OpIAdd %int %25 %int_5
+ OpStore %outparm %20
+ OpReturn
+ OpFunctionEnd)";
+
+ const std::string after = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %y %outparm
+OpExecutionMode %main OriginUpperLeft
+OpName %main "main"
+OpName %y "y"
+OpName %outparm "outparm"
+OpDecorate %y Flat
+OpDecorate %y Location 0
+OpDecorate %outparm Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%y = OpVariable %_ptr_Input_int Input
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+%int_23 = OpConstant %int 23
+%int_5 = OpConstant %int 5
+%_ptr_Output_int = OpTypePointer Output %int
+%outparm = OpVariable %_ptr_Output_int Output
+%15 = OpUndef %int
+%main = OpFunction %void None %6
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%22 = OpPhi %int %15 %16
+%23 = OpIAdd %int %22 %int_5
+OpStore %outparm %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CFGCleanupPass>(before, after, true, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/code_sink_test.cpp b/test/opt/code_sink_test.cpp
new file mode 100644
index 0000000..9b86c66
--- /dev/null
+++ b/test/opt/code_sink_test.cpp
@@ -0,0 +1,533 @@
+// Copyright (c) 2019 Google LLC
+//
+// 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 "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CodeSinkTest = PassTest<::testing::Test>;
+
+TEST_F(CodeSinkTest, MoveToNextBlock) {
+ const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %9 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %10 = OpTypeFunction %void
+ %1 = OpFunction %void None %10
+ %11 = OpLabel
+ %12 = OpAccessChain %_ptr_Uniform_uint %9 %uint_0
+ %13 = OpLoad %uint %12
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpCopyObject %uint %13
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MovePastSelection) {
+ const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK: [[merge_bb]] = OpLabel
+;CHECK: [[ac:%\w+]] = OpAccessChain
+;CHECK: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK: OpCopyObject %uint [[ld]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %16
+ %17 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, MoveIntoSelection) {
+ const std::string text = R"(
+;CHECK: OpFunction
+;CHECK: OpLabel
+;CHECK: OpSelectionMerge [[merge_bb:%\w+]]
+;CHECK-NEXT: OpBranchConditional %true [[bb:%\w+]] [[merge_bb]]
+;CHECK: [[bb]] = OpLabel
+;CHECK-NEXT: [[ac:%\w+]] = OpAccessChain
+;CHECK-NEXT: [[ld:%\w+]] = OpLoad %uint [[ac]]
+;CHECK-NEXT: OpCopyObject %uint [[ld]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CodeSinkingPass>(text, true);
+}
+
+TEST_F(CodeSinkTest, LeaveBeforeSelection) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %20
+ %20 = OpLabel
+ OpBranch %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ %19 = OpCopyObject %uint %15
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, LeaveAloneUseInSameBlock) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ %cond = OpIEqual %bool %15 %uint_0
+ OpSelectionMerge %16 None
+ OpBranchConditional %cond %17 %16
+ %17 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %19 = OpCopyObject %uint %15
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpBranch %17
+ %17 = OpLabel
+ OpLoopMerge %merge %cont None
+ OpBranch %cont
+ %cont = OpLabel
+ %cond = OpIEqual %bool %15 %uint_0
+ OpBranchConditional %cond %merge %17
+ %merge = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveIntoLoop2) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %16
+ %17 = OpLabel
+ OpLoopMerge %merge %cont None
+ OpBranch %cont
+ %cont = OpLabel
+ %cond = OpIEqual %bool %15 %uint_0
+ OpBranchConditional %cond %merge %17
+ %merge = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveSelectionUsedInBothSides) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %20
+ %20 = OpLabel
+ %19 = OpCopyObject %uint %15
+ OpBranch %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfStore) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpStore %14 %15
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %20
+ %20 = OpLabel
+ OpBranch %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveReadOnlyLoadWithSync) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeArray %uint %uint_4
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpMemoryBarrier %uint_4 %mem_semantics
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %20
+ %20 = OpLabel
+ OpBranch %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfSync) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpDecorate %_arr_uint_uint_4 BufferBlock
+ OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ OpMemoryBarrier %uint_4 %mem_semantics
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %20
+ %20 = OpLabel
+ OpBranch %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, DontMoveBecauseOfAtomicWithSync) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpDecorate %_arr_uint_uint_4 BufferBlock
+ OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%mem_semantics = OpConstant %uint 0x42 ; Uniform memeory arquire
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ %al = OpAtomicLoad %uint %14 %uint_4 %mem_semantics
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %20
+ %20 = OpLabel
+ OpBranch %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CodeSinkTest, MoveWithAtomicWithoutSync) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %1 "main"
+ OpDecorate %_arr_uint_uint_4 BufferBlock
+ OpMemberDecorate %_arr_uint_uint_4 0 Offset 0
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_4 = OpConstant %uint 4
+%_arr_uint_uint_4 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%_ptr_Uniform__arr_uint_uint_4 = OpTypePointer Uniform %_arr_uint_uint_4
+ %11 = OpVariable %_ptr_Uniform__arr_uint_uint_4 Uniform
+ %12 = OpTypeFunction %void
+ %1 = OpFunction %void None %12
+ %13 = OpLabel
+ %14 = OpAccessChain %_ptr_Uniform_uint %11 %uint_0
+ %15 = OpLoad %uint %14
+ %al = OpAtomicLoad %uint %14 %uint_4 %uint_0
+ OpSelectionMerge %16 None
+ OpBranchConditional %true %17 %20
+ %20 = OpLabel
+ OpBranch %16
+ %17 = OpLabel
+ %18 = OpCopyObject %uint %15
+ OpBranch %16
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CodeSinkingPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ true);
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/combine_access_chains_test.cpp b/test/opt/combine_access_chains_test.cpp
new file mode 100644
index 0000000..aed14c9
--- /dev/null
+++ b/test/opt/combine_access_chains_test.cpp
@@ -0,0 +1,773 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CombineAccessChainsTest = PassTest<::testing::Test>;
+
+TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainConstant) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int3]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpAccessChain %ptr_Workgroup_uint %var %uint_0
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, PtrAccessChainFromInBoundsAccessChainConstant) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int3]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpInBoundsAccessChain %ptr_Workgroup_uint %var %uint_0
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainCombineConstant) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
+; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int2]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpAccessChain %ptr_Workgroup_uint %var %uint_1
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainNonConstant) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[ld1:%\w+]] = OpLoad
+; CHECK: [[ld2:%\w+]] = OpLoad
+; CHECK: [[add:%\w+]] = OpIAdd [[int]] [[ld1]] [[ld2]]
+; CHECK: OpAccessChain [[ptr_int]] [[var]] [[add]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Function_uint = OpTypePointer Function %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%local_var = OpVariable %ptr_Function_uint Function
+%ld1 = OpLoad %uint %local_var
+%gep = OpAccessChain %ptr_Workgroup_uint %var %ld1
+%ld2 = OpLoad %uint %local_var
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainExtraIndices) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
+; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
+; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]] [[int3]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
+%uint_array_4_array_4_array_4 = OpTypeArray %uint_array_4_array_4 %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Function_uint = OpTypePointer Function %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
+%ptr_Workgroup_uint_array_4_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4_array_4_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1 %uint_0
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_2 %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest,
+ PtrAccessChainFromPtrAccessChainCombineElementOperand) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
+; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int6]] [[int3]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3 %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest,
+ PtrAccessChainFromPtrAccessChainOnlyElementOperand) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
+; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
+; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
+; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest,
+ PtrAccessChainFromPtrAccessCombineNonElementIndex) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int3]] [[int3]] [[int3]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Function_uint = OpTypePointer Function %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3 %uint_0
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3 %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest,
+ AccessChainFromPtrAccessChainOnlyElementOperand) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int3]] [[int3]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
+%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, AccessChainFromPtrAccessChainAppend) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
+; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
+; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]] [[int3]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1 %uint_2
+%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, AccessChainFromAccessChainAppend) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
+; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
+; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%ptr_gep = OpAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1
+%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, NonConstantStructSlide) {
+ const std::string text = R"(
+; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[ld:%\w+]] = OpLoad
+; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[ld]] [[int0]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%struct = OpTypeStruct %uint %uint
+%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Function_uint = OpTypePointer Function %uint
+%wg_var = OpVariable %ptr_Workgroup_struct Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%1 = OpLabel
+%func_var = OpVariable %ptr_Function_uint Function
+%ld = OpLoad %uint %func_var
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_struct %wg_var %ld
+%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, DontCombineNonConstantStructSlide) {
+ const std::string text = R"(
+; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
+; CHECK: [[ld:%\w+]] = OpLoad
+; CHECK: [[gep:%\w+]] = OpAccessChain
+; CHECK: OpPtrAccessChain {{%\w+}} [[gep]] [[ld]] [[int0]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_4 = OpConstant %uint 4
+%struct = OpTypeStruct %uint %uint
+%struct_array_4 = OpTypeArray %struct %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Function_uint = OpTypePointer Function %uint
+%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
+%ptr_Workgroup_struct_array_4 = OpTypePointer Workgroup %struct_array_4
+%wg_var = OpVariable %ptr_Workgroup_struct_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%1 = OpLabel
+%func_var = OpVariable %ptr_Function_uint Function
+%ld = OpLoad %uint %func_var
+%gep = OpAccessChain %ptr_Workgroup_struct %wg_var %uint_0
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld %uint_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, CombineNonConstantStructSlideElement) {
+ const std::string text = R"(
+; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[ld:%\w+]] = OpLoad
+; CHECK: [[add:%\w+]] = OpIAdd {{%\w+}} [[ld]] [[ld]]
+; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[add]] [[int0]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_4 = OpConstant %uint 4
+%struct = OpTypeStruct %uint %uint
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Function_uint = OpTypePointer Function %uint
+%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
+%wg_var = OpVariable %ptr_Workgroup_struct Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%1 = OpLabel
+%func_var = OpVariable %ptr_Function_uint Function
+%ld = OpLoad %uint %func_var
+%gep = OpPtrAccessChain %ptr_Workgroup_struct %wg_var %ld
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld %uint_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, PtrAccessChainFromInBoundsPtrAccessChain) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
+; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
+; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
+; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
+OpCapability Shader
+OpCapability VariablePointers
+OpCapability Addresses
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
+%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, InBoundsPtrAccessChainFromPtrAccessChain) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
+; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
+; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
+; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
+OpCapability Shader
+OpCapability VariablePointers
+OpCapability Addresses
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
+%ptr_gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest,
+ InBoundsPtrAccessChainFromInBoundsPtrAccessChain) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
+; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
+; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
+; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
+; CHECK: OpInBoundsPtrAccessChain [[ptr_array]] [[var]] [[int6]]
+OpCapability Shader
+OpCapability VariablePointers
+OpCapability Addresses
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_array_4 = OpTypeArray %uint %uint_4
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
+%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%main_lab = OpLabel
+%gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
+%ptr_gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, NoIndexAccessChains) {
+ const std::string text = R"(
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK-NOT: OpConstant
+; CHECK: [[gep:%\w+]] = OpAccessChain {{%\w+}} [[var]]
+; CHECK: OpAccessChain {{%\w+}} [[var]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%var = OpVariable %ptr_Workgroup_uint Workgroup
+%void_func = OpTypeFunction %void
+%func = OpFunction %void None %void_func
+%1 = OpLabel
+%gep1 = OpAccessChain %ptr_Workgroup_uint %var
+%gep2 = OpAccessChain %ptr_Workgroup_uint %gep1
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, NoIndexPtrAccessChains) {
+ const std::string text = R"(
+; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: [[gep:%\w+]] = OpPtrAccessChain {{%\w+}} [[var]] [[int0]]
+; CHECK: OpCopyObject {{%\w+}} [[gep]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%var = OpVariable %ptr_Workgroup_uint Workgroup
+%void_func = OpTypeFunction %void
+%func = OpFunction %void None %void_func
+%1 = OpLabel
+%gep1 = OpPtrAccessChain %ptr_Workgroup_uint %var %uint_0
+%gep2 = OpAccessChain %ptr_Workgroup_uint %gep1
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, NoIndexPtrAccessChains2) {
+ const std::string text = R"(
+; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[int0]]
+OpCapability Shader
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%var = OpVariable %ptr_Workgroup_uint Workgroup
+%void_func = OpTypeFunction %void
+%func = OpFunction %void None %void_func
+%1 = OpLabel
+%gep1 = OpAccessChain %ptr_Workgroup_uint %var
+%gep2 = OpPtrAccessChain %ptr_Workgroup_uint %gep1 %uint_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+TEST_F(CombineAccessChainsTest, CombineMixedSign) {
+ const std::string text = R"(
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: [[uint2:%\w+]] = OpConstant [[uint]] 2
+; CHECK: OpInBoundsPtrAccessChain {{%\w+}} [[var]] [[uint2]]
+OpCapability Shader
+OpCapability VariablePointers
+OpCapability Addresses
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+OpExecutionMode %func OriginUpperLeft
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%int = OpTypeInt 32 1
+%uint_1 = OpConstant %uint 1
+%int_1 = OpConstant %int 1
+%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
+%var = OpVariable %ptr_Workgroup_uint Workgroup
+%void_func = OpTypeFunction %void
+%func = OpFunction %void None %void_func
+%1 = OpLabel
+%gep1 = OpInBoundsPtrAccessChain %ptr_Workgroup_uint %var %uint_1
+%gep2 = OpInBoundsPtrAccessChain %ptr_Workgroup_uint %gep1 %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<CombineAccessChains>(text, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/common_uniform_elim_test.cpp b/test/opt/common_uniform_elim_test.cpp
new file mode 100644
index 0000000..9e39439
--- /dev/null
+++ b/test/opt/common_uniform_elim_test.cpp
@@ -0,0 +1,1394 @@
+// 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"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CommonUniformElimTest = PassTest<::testing::Test>;
+
+TEST_F(CommonUniformElimTest, Basic1) {
+ // Note: This test exemplifies the following:
+ // - Common uniform (%_) load floated to nearest non-controlled block
+ // - Common extract (g_F) floated to non-controlled block
+ // - Non-common extract (g_F2) not floated, but common uniform load shared
+ //
+ // #version 140
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // float g_F;
+ // float g_F2;
+ // } ;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // if (fi > 0) {
+ // v = v * g_F;
+ // }
+ // else {
+ // float f2 = g_F2 - g_F;
+ // v = v * f2;
+ // }
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpName %gl_FragColor "gl_FragColor"
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%11 = 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
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%U_t = OpTypeStruct %float %float
+%_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_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpLoad %float %fi
+%29 = OpFOrdGreaterThan %bool %28 %float_0
+OpSelectionMerge %30 None
+OpBranchConditional %29 %31 %32
+%31 = OpLabel
+%33 = OpLoad %v4float %v
+%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%35 = OpLoad %float %34
+%36 = OpVectorTimesScalar %v4float %33 %35
+OpStore %v %36
+OpBranch %30
+%32 = OpLabel
+%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%38 = OpLoad %float %37
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%40 = OpLoad %float %39
+%41 = OpFSub %float %38 %40
+OpStore %f2 %41
+%42 = OpLoad %v4float %v
+%43 = OpLoad %float %f2
+%44 = OpVectorTimesScalar %v4float %42 %43
+OpStore %v %44
+OpBranch %30
+%30 = OpLabel
+%45 = OpLoad %v4float %v
+OpStore %gl_FragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%52 = OpLoad %U_t %_
+%53 = OpCompositeExtract %float %52 0
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpLoad %float %fi
+%29 = OpFOrdGreaterThan %bool %28 %float_0
+OpSelectionMerge %30 None
+OpBranchConditional %29 %31 %32
+%31 = OpLabel
+%33 = OpLoad %v4float %v
+%36 = OpVectorTimesScalar %v4float %33 %53
+OpStore %v %36
+OpBranch %30
+%32 = OpLabel
+%49 = OpCompositeExtract %float %52 1
+%41 = OpFSub %float %49 %53
+OpStore %f2 %41
+%42 = OpLoad %v4float %v
+%43 = OpLoad %float %f2
+%44 = OpVectorTimesScalar %v4float %42 %43
+OpStore %v %44
+OpBranch %30
+%30 = OpLabel
+%45 = OpLoad %v4float %v
+OpStore %gl_FragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(CommonUniformElimTest, Basic2) {
+ // Note: This test exemplifies the following:
+ // - Common uniform (%_) load floated to nearest non-controlled block
+ // - Common extract (g_F) floated to non-controlled block
+ // - Non-common extract (g_F2) not floated, but common uniform load shared
+ //
+ // #version 140
+ // in vec4 BaseColor;
+ // in float fi;
+ // in float fi2;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // float g_F;
+ // float g_F2;
+ // } ;
+ //
+ // void main()
+ // {
+ // float f = fi;
+ // if (f < 0)
+ // f = -f;
+ // if (fi2 > 0) {
+ // f = f * g_F;
+ // }
+ // else {
+ // f = g_F2 - g_F;
+ // }
+ // gl_FragColor = f * BaseColor;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %fi %fi2 %gl_FragColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %f "f"
+OpName %fi "fi"
+OpName %fi2 "fi2"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %gl_FragColor "gl_FragColor"
+OpName %BaseColor "BaseColor"
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%fi2 = OpVariable %_ptr_Input_float Input
+%U_t = OpTypeStruct %float %float
+%_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_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%v4float = OpTypeVector %float 4
+%_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 %11
+%25 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%26 = OpLoad %float %fi
+OpStore %f %26
+%27 = OpLoad %float %f
+%28 = OpFOrdLessThan %bool %27 %float_0
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+%31 = OpLoad %float %f
+%32 = OpFNegate %float %31
+OpStore %f %32
+OpBranch %29
+%29 = OpLabel
+%33 = OpLoad %float %fi2
+%34 = OpFOrdGreaterThan %bool %33 %float_0
+OpSelectionMerge %35 None
+OpBranchConditional %34 %36 %37
+%36 = OpLabel
+%38 = OpLoad %float %f
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%40 = OpLoad %float %39
+%41 = OpFMul %float %38 %40
+OpStore %f %41
+OpBranch %35
+%37 = OpLabel
+%42 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%43 = OpLoad %float %42
+%44 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%45 = OpLoad %float %44
+%46 = OpFSub %float %43 %45
+OpStore %f %46
+OpBranch %35
+%35 = OpLabel
+%47 = OpLoad %v4float %BaseColor
+%48 = OpLoad %float %f
+%49 = OpVectorTimesScalar %v4float %47 %48
+OpStore %gl_FragColor %49
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %11
+%25 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%26 = OpLoad %float %fi
+OpStore %f %26
+%27 = OpLoad %float %f
+%28 = OpFOrdLessThan %bool %27 %float_0
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+%31 = OpLoad %float %f
+%32 = OpFNegate %float %31
+OpStore %f %32
+OpBranch %29
+%29 = OpLabel
+%56 = OpLoad %U_t %_
+%57 = OpCompositeExtract %float %56 0
+%33 = OpLoad %float %fi2
+%34 = OpFOrdGreaterThan %bool %33 %float_0
+OpSelectionMerge %35 None
+OpBranchConditional %34 %36 %37
+%36 = OpLabel
+%38 = OpLoad %float %f
+%41 = OpFMul %float %38 %57
+OpStore %f %41
+OpBranch %35
+%37 = OpLabel
+%53 = OpCompositeExtract %float %56 1
+%46 = OpFSub %float %53 %57
+OpStore %f %46
+OpBranch %35
+%35 = OpLabel
+%47 = OpLoad %v4float %BaseColor
+%48 = OpLoad %float %f
+%49 = OpVectorTimesScalar %v4float %47 %48
+OpStore %gl_FragColor %49
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(CommonUniformElimTest, Basic3) {
+ // Note: This test exemplifies the following:
+ // - Existing common uniform (%_) load kept in place and shared
+ //
+ // #version 140
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // bool g_B;
+ // float g_F;
+ // } ;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // if (g_B)
+ // v = v * g_F;
+ // 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 %fi
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_B"
+OpMemberName %U_t 1 "g_F"
+OpName %_ ""
+OpName %gl_FragColor "gl_FragColor"
+OpName %fi "fi"
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%10 = 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
+%uint = OpTypeInt 32 0
+%U_t = OpTypeStruct %uint %float
+%_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
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%int_1 = OpConstant %int 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpINotEqual %bool %29 %uint_0
+OpSelectionMerge %31 None
+OpBranchConditional %30 %32 %31
+%32 = OpLabel
+%33 = OpLoad %v4float %v
+%34 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%35 = OpLoad %float %34
+%36 = OpVectorTimesScalar %v4float %33 %35
+OpStore %v %36
+OpBranch %31
+%31 = OpLabel
+%37 = OpLoad %v4float %v
+OpStore %gl_FragColor %37
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%38 = OpLoad %U_t %_
+%39 = OpCompositeExtract %uint %38 0
+%30 = OpINotEqual %bool %39 %uint_0
+OpSelectionMerge %31 None
+OpBranchConditional %30 %32 %31
+%32 = OpLabel
+%33 = OpLoad %v4float %v
+%41 = OpCompositeExtract %float %38 1
+%36 = OpVectorTimesScalar %v4float %33 %41
+OpStore %v %36
+OpBranch %31
+%31 = OpLabel
+%37 = OpLoad %v4float %v
+OpStore %gl_FragColor %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(CommonUniformElimTest, Loop) {
+ // Note: This test exemplifies the following:
+ // - Common extract (g_F) shared between two loops
+ // #version 140
+ // in vec4 BC;
+ // in vec4 BC2;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // float g_F;
+ // } ;
+ //
+ // void main()
+ // {
+ // vec4 v = BC;
+ // for (int i = 0; i < 4; i++)
+ // v[i] = v[i] / g_F;
+ // vec4 v2 = BC2;
+ // for (int i = 0; i < 4; i++)
+ // v2[i] = v2[i] * g_F;
+ // gl_FragColor = v + v2;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %BC2 %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BC "BC"
+OpName %i "i"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpName %_ ""
+OpName %v2 "v2"
+OpName %BC2 "BC2"
+OpName %i_0 "i"
+OpName %gl_FragColor "gl_FragColor"
+OpMemberDecorate %U_t 0 Offset 0
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BC = OpVariable %_ptr_Input_v4float Input
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_4 = OpConstant %int 4
+%bool = OpTypeBool
+%_ptr_Function_float = OpTypePointer Function %float
+%U_t = OpTypeStruct %float
+%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
+%_ = OpVariable %_ptr_Uniform_U_t Uniform
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%int_1 = OpConstant %int 1
+%BC2 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %13
+%28 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%i = OpVariable %_ptr_Function_int Function
+%v2 = OpVariable %_ptr_Function_v4float Function
+%i_0 = OpVariable %_ptr_Function_int Function
+%29 = OpLoad %v4float %BC
+OpStore %v %29
+OpStore %i %int_0
+OpBranch %30
+%30 = OpLabel
+OpLoopMerge %31 %32 None
+OpBranch %33
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSLessThan %bool %34 %int_4
+OpBranchConditional %35 %36 %31
+%36 = OpLabel
+%37 = OpLoad %int %i
+%38 = OpLoad %int %i
+%39 = OpAccessChain %_ptr_Function_float %v %38
+%40 = OpLoad %float %39
+%41 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%42 = OpLoad %float %41
+%43 = OpFDiv %float %40 %42
+%44 = OpAccessChain %_ptr_Function_float %v %37
+OpStore %44 %43
+OpBranch %32
+%32 = OpLabel
+%45 = OpLoad %int %i
+%46 = OpIAdd %int %45 %int_1
+OpStore %i %46
+OpBranch %30
+%31 = OpLabel
+%47 = OpLoad %v4float %BC2
+OpStore %v2 %47
+OpStore %i_0 %int_0
+OpBranch %48
+%48 = OpLabel
+OpLoopMerge %49 %50 None
+OpBranch %51
+%51 = OpLabel
+%52 = OpLoad %int %i_0
+%53 = OpSLessThan %bool %52 %int_4
+OpBranchConditional %53 %54 %49
+%54 = OpLabel
+%55 = OpLoad %int %i_0
+%56 = OpLoad %int %i_0
+%57 = OpAccessChain %_ptr_Function_float %v2 %56
+%58 = OpLoad %float %57
+%59 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%60 = OpLoad %float %59
+%61 = OpFMul %float %58 %60
+%62 = OpAccessChain %_ptr_Function_float %v2 %55
+OpStore %62 %61
+OpBranch %50
+%50 = OpLabel
+%63 = OpLoad %int %i_0
+%64 = OpIAdd %int %63 %int_1
+OpStore %i_0 %64
+OpBranch %48
+%49 = OpLabel
+%65 = OpLoad %v4float %v
+%66 = OpLoad %v4float %v2
+%67 = OpFAdd %v4float %65 %66
+OpStore %gl_FragColor %67
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %13
+%28 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%i = OpVariable %_ptr_Function_int Function
+%v2 = OpVariable %_ptr_Function_v4float Function
+%i_0 = OpVariable %_ptr_Function_int Function
+%72 = OpLoad %U_t %_
+%73 = OpCompositeExtract %float %72 0
+%29 = OpLoad %v4float %BC
+OpStore %v %29
+OpStore %i %int_0
+OpBranch %30
+%30 = OpLabel
+OpLoopMerge %31 %32 None
+OpBranch %33
+%33 = OpLabel
+%34 = OpLoad %int %i
+%35 = OpSLessThan %bool %34 %int_4
+OpBranchConditional %35 %36 %31
+%36 = OpLabel
+%37 = OpLoad %int %i
+%38 = OpLoad %int %i
+%39 = OpAccessChain %_ptr_Function_float %v %38
+%40 = OpLoad %float %39
+%43 = OpFDiv %float %40 %73
+%44 = OpAccessChain %_ptr_Function_float %v %37
+OpStore %44 %43
+OpBranch %32
+%32 = OpLabel
+%45 = OpLoad %int %i
+%46 = OpIAdd %int %45 %int_1
+OpStore %i %46
+OpBranch %30
+%31 = OpLabel
+%47 = OpLoad %v4float %BC2
+OpStore %v2 %47
+OpStore %i_0 %int_0
+OpBranch %48
+%48 = OpLabel
+OpLoopMerge %49 %50 None
+OpBranch %51
+%51 = OpLabel
+%52 = OpLoad %int %i_0
+%53 = OpSLessThan %bool %52 %int_4
+OpBranchConditional %53 %54 %49
+%54 = OpLabel
+%55 = OpLoad %int %i_0
+%56 = OpLoad %int %i_0
+%57 = OpAccessChain %_ptr_Function_float %v2 %56
+%58 = OpLoad %float %57
+%61 = OpFMul %float %58 %73
+%62 = OpAccessChain %_ptr_Function_float %v2 %55
+OpStore %62 %61
+OpBranch %50
+%50 = OpLabel
+%63 = OpLoad %int %i_0
+%64 = OpIAdd %int %63 %int_1
+OpStore %i_0 %64
+OpBranch %48
+%49 = OpLabel
+%65 = OpLoad %v4float %v
+%66 = OpLoad %v4float %v2
+%67 = OpFAdd %v4float %65 %66
+OpStore %gl_FragColor %67
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(CommonUniformElimTest, Volatile1) {
+ // Note: This test exemplifies the following:
+ // - Same test as Basic1 with the exception that
+ // the Load of g_F in else-branch is volatile
+ // - Common uniform (%_) load floated to nearest non-controlled block
+ //
+ // #version 140
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // float g_F;
+ // float g_F2;
+ // } ;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // if (fi > 0) {
+ // v = v * g_F;
+ // }
+ // else {
+ // float f2 = g_F2 - g_F;
+ // v = v * f2;
+ // }
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpName %gl_FragColor "gl_FragColor"
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%11 = 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
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%U_t = OpTypeStruct %float %float
+%_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_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpLoad %float %fi
+%29 = OpFOrdGreaterThan %bool %28 %float_0
+OpSelectionMerge %30 None
+OpBranchConditional %29 %31 %32
+%31 = OpLabel
+%33 = OpLoad %v4float %v
+%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%35 = OpLoad %float %34
+%36 = OpVectorTimesScalar %v4float %33 %35
+OpStore %v %36
+OpBranch %30
+%32 = OpLabel
+%37 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%38 = OpLoad %float %37
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%40 = OpLoad %float %39 Volatile
+%41 = OpFSub %float %38 %40
+OpStore %f2 %41
+%42 = OpLoad %v4float %v
+%43 = OpLoad %float %f2
+%44 = OpVectorTimesScalar %v4float %42 %43
+OpStore %v %44
+OpBranch %30
+%30 = OpLabel
+%45 = OpLoad %v4float %v
+OpStore %gl_FragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %11
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%50 = OpLoad %U_t %_
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpLoad %float %fi
+%29 = OpFOrdGreaterThan %bool %28 %float_0
+OpSelectionMerge %30 None
+OpBranchConditional %29 %31 %32
+%31 = OpLabel
+%33 = OpLoad %v4float %v
+%47 = OpCompositeExtract %float %50 0
+%36 = OpVectorTimesScalar %v4float %33 %47
+OpStore %v %36
+OpBranch %30
+%32 = OpLabel
+%49 = OpCompositeExtract %float %50 1
+%39 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%40 = OpLoad %float %39 Volatile
+%41 = OpFSub %float %49 %40
+OpStore %f2 %41
+%42 = OpLoad %v4float %v
+%43 = OpLoad %float %f2
+%44 = OpVectorTimesScalar %v4float %42 %43
+OpStore %v %44
+OpBranch %30
+%30 = OpLabel
+%45 = OpLoad %v4float %v
+OpStore %gl_FragColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(CommonUniformElimTest, Volatile2) {
+ // Note: This test exemplifies the following:
+ // - Same test as Basic1 with the exception that
+ // U_t is Volatile.
+ // - No optimizations are applied
+ //
+ // #version 430
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // layout(std430) volatile buffer U_t
+ // {
+ // float g_F;
+ // float g_F2;
+ // };
+ //
+ //
+ // void main(void)
+ // {
+ // vec4 v = BaseColor;
+ // if (fi > 0) {
+ // v = v * g_F;
+ // } else {
+ // float f2 = g_F2 - g_F;
+ // v = v * f2;
+ // }
+ // }
+
+ const std::string text =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpDecorate %BaseColor Location 0
+OpDecorate %fi Location 0
+OpMemberDecorate %U_t 0 Volatile
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Volatile
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%3 = 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
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%U_t = OpTypeStruct %float %float
+%_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_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %3
+%5 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%12 = OpLoad %v4float %BaseColor
+OpStore %v %12
+%15 = OpLoad %float %fi
+%18 = OpFOrdGreaterThan %bool %15 %float_0
+OpSelectionMerge %20 None
+OpBranchConditional %18 %19 %31
+%19 = OpLabel
+%21 = OpLoad %v4float %v
+%28 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%29 = OpLoad %float %28
+%30 = OpVectorTimesScalar %v4float %21 %29
+OpStore %v %30
+OpBranch %20
+%31 = OpLabel
+%35 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%36 = OpLoad %float %35
+%37 = OpAccessChain %_ptr_Uniform_float %_ %int_0
+%38 = OpLoad %float %37
+%39 = OpFSub %float %36 %38
+OpStore %f2 %39
+%40 = OpLoad %v4float %v
+%41 = OpLoad %float %f2
+%42 = OpVectorTimesScalar %v4float %40 %41
+OpStore %v %42
+OpBranch %20
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ Pass::Status res = std::get<1>(
+ SinglePassRunAndDisassemble<CommonUniformElimPass>(text, true, false));
+ EXPECT_EQ(res, Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(CommonUniformElimTest, Volatile3) {
+ // Note: This test exemplifies the following:
+ // - Same test as Volatile2 with the exception that
+ // the nested struct S is volatile
+ // - No optimizations are applied
+ //
+ // #version 430
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // struct S {
+ // volatile float a;
+ // };
+ //
+ // layout(std430) buffer U_t
+ // {
+ // S g_F;
+ // S g_F2;
+ // };
+ //
+ //
+ // void main(void)
+ // {
+ // vec4 v = BaseColor;
+ // if (fi > 0) {
+ // v = v * g_F.a;
+ // } else {
+ // float f2 = g_F2.a - g_F.a;
+ // v = v * f2;
+ // }
+ // }
+
+ const std::string text =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %S "S"
+OpMemberName %S 0 "a"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_F"
+OpMemberName %U_t 1 "g_F2"
+OpName %_ ""
+OpName %f2 "f2"
+OpDecorate %BaseColor Location 0
+OpDecorate %fi Location 0
+OpMemberDecorate %S 0 Offset 0
+OpMemberDecorate %S 0 Volatile
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%3 = 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
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%S = OpTypeStruct %float
+%U_t = OpTypeStruct %S %S
+%_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_float = OpTypePointer Uniform %float
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %3
+%5 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%12 = OpLoad %v4float %BaseColor
+OpStore %v %12
+%15 = OpLoad %float %fi
+%18 = OpFOrdGreaterThan %bool %15 %float_0
+OpSelectionMerge %20 None
+OpBranchConditional %18 %19 %32
+%19 = OpLabel
+%21 = OpLoad %v4float %v
+%29 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0
+%30 = OpLoad %float %29
+%31 = OpVectorTimesScalar %v4float %21 %30
+OpStore %v %31
+OpBranch %20
+%32 = OpLabel
+%36 = OpAccessChain %_ptr_Uniform_float %_ %int_1 %int_0
+%37 = OpLoad %float %36
+%38 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %int_0
+%39 = OpLoad %float %38
+%40 = OpFSub %float %37 %39
+OpStore %f2 %40
+%41 = OpLoad %v4float %v
+%42 = OpLoad %float %f2
+%43 = OpVectorTimesScalar %v4float %41 %42
+OpStore %v %43
+OpBranch %20
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ Pass::Status res = std::get<1>(
+ SinglePassRunAndDisassemble<CommonUniformElimPass>(text, true, false));
+ EXPECT_EQ(res, Pass::Status::SuccessWithoutChange);
+}
+
+TEST_F(CommonUniformElimTest, IteratorDanglingPointer) {
+ // Note: This test exemplifies the following:
+ // - Existing common uniform (%_) load kept in place and shared
+ //
+ // #version 140
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // layout(std140) uniform U_t
+ // {
+ // bool g_B;
+ // float g_F;
+ // } ;
+ //
+ // uniform float alpha;
+ // uniform bool alpha_B;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // if (g_B) {
+ // v = v * g_F;
+ // if (alpha_B)
+ // v = v * alpha;
+ // else
+ // v = v * fi;
+ // }
+ // 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 %fi
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_B"
+OpMemberName %U_t 1 "g_F"
+OpName %alpha "alpha"
+OpName %alpha_B "alpha_B"
+OpName %_ ""
+OpName %gl_FragColor "gl_FragColor"
+OpName %fi "fi"
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 4
+OpDecorate %U_t Block
+OpDecorate %_ DescriptorSet 0
+%void = OpTypeVoid
+%12 = 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
+%uint = OpTypeInt 32 0
+%U_t = OpTypeStruct %uint %float
+%_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
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%int_1 = OpConstant %int 1
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%alpha = OpVariable %_ptr_Uniform_float Uniform
+%alpha_B = OpVariable %_ptr_Uniform_uint Uniform
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %12
+%26 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%27 = OpLoad %v4float %BaseColor
+OpStore %v %27
+%28 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpINotEqual %bool %29 %uint_0
+OpSelectionMerge %31 None
+OpBranchConditional %30 %31 %32
+%32 = OpLabel
+%47 = OpLoad %v4float %v
+OpStore %gl_FragColor %47
+OpReturn
+%31 = OpLabel
+%33 = OpAccessChain %_ptr_Uniform_float %_ %int_1
+%34 = OpLoad %float %33
+%35 = OpLoad %v4float %v
+%36 = OpVectorTimesScalar %v4float %35 %34
+OpStore %v %36
+%37 = OpLoad %uint %alpha_B
+%38 = OpIEqual %bool %37 %uint_0
+OpSelectionMerge %43 None
+OpBranchConditional %38 %43 %39
+%39 = OpLabel
+%40 = OpLoad %float %alpha
+%41 = OpLoad %v4float %v
+%42 = OpVectorTimesScalar %v4float %41 %40
+OpStore %v %42
+OpBranch %50
+%50 = OpLabel
+%51 = OpLoad %v4float %v
+OpStore %gl_FragColor %51
+OpReturn
+%43 = OpLabel
+%44 = OpLoad %float %fi
+%45 = OpLoad %v4float %v
+%46 = OpVectorTimesScalar %v4float %45 %44
+OpStore %v %46
+OpBranch %60
+%60 = OpLabel
+%61 = OpLoad %v4float %v
+OpStore %gl_FragColor %61
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %12
+%28 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%29 = OpLoad %v4float %BaseColor
+OpStore %v %29
+%54 = OpLoad %U_t %_
+%55 = OpCompositeExtract %uint %54 0
+%32 = OpINotEqual %bool %55 %uint_0
+OpSelectionMerge %33 None
+OpBranchConditional %32 %33 %34
+%34 = OpLabel
+%35 = OpLoad %v4float %v
+OpStore %gl_FragColor %35
+OpReturn
+%33 = OpLabel
+%58 = OpLoad %float %alpha
+%57 = OpCompositeExtract %float %54 1
+%38 = OpLoad %v4float %v
+%39 = OpVectorTimesScalar %v4float %38 %57
+OpStore %v %39
+%40 = OpLoad %uint %alpha_B
+%41 = OpIEqual %bool %40 %uint_0
+OpSelectionMerge %42 None
+OpBranchConditional %41 %42 %43
+%43 = OpLabel
+%45 = OpLoad %v4float %v
+%46 = OpVectorTimesScalar %v4float %45 %58
+OpStore %v %46
+OpBranch %47
+%47 = OpLabel
+%48 = OpLoad %v4float %v
+OpStore %gl_FragColor %48
+OpReturn
+%42 = OpLabel
+%49 = OpLoad %float %fi
+%50 = OpLoad %v4float %v
+%51 = OpVectorTimesScalar %v4float %50 %49
+OpStore %v %51
+OpBranch %52
+%52 = OpLabel
+%53 = OpLoad %v4float %v
+OpStore %gl_FragColor %53
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<CommonUniformElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(CommonUniformElimTest, MixedConstantAndNonConstantIndexes) {
+ const std::string text = R"(
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Uniform
+; CHECK: %501 = OpLabel
+; CHECK: [[ld:%\w+]] = OpLoad
+; CHECK-NOT: OpCompositeExtract {{%\w+}} {{%\w+}} 0 2 484
+; CHECK: OpAccessChain {{%\w+}} [[var]] %int_0 %int_2 [[ld]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "ringeffectLayer_px" %gl_FragCoord %178 %182
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource HLSL 500
+ OpDecorate %_arr_v4float_uint_10 ArrayStride 16
+ OpMemberDecorate %_struct_20 0 Offset 0
+ OpMemberDecorate %_struct_20 1 Offset 16
+ OpMemberDecorate %_struct_20 2 Offset 32
+ OpMemberDecorate %_struct_21 0 Offset 0
+ OpDecorate %_struct_21 Block
+ OpDecorate %23 DescriptorSet 0
+ OpDecorate %gl_FragCoord BuiltIn FragCoord
+ OpDecorate %178 Location 0
+ OpDecorate %182 Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+ %uint = OpTypeInt 32 0
+ %uint_10 = OpConstant %uint 10
+%_arr_v4float_uint_10 = OpTypeArray %v4float %uint_10
+ %_struct_20 = OpTypeStruct %v4float %v4float %_arr_v4float_uint_10
+ %_struct_21 = OpTypeStruct %_struct_20
+%_ptr_Uniform__struct_21 = OpTypePointer Uniform %_struct_21
+ %23 = OpVariable %_ptr_Uniform__struct_21 Uniform
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_ptr_Uniform_float = OpTypePointer Uniform %float
+ %uint_3 = OpConstant %uint 3
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %float_0 = OpConstant %float 0
+ %43 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_5 = OpConstant %int 5
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %uint_5 = OpConstant %uint 5
+%_arr_v2float_uint_5 = OpTypeArray %v2float %uint_5
+%_ptr_Function__arr_v2float_uint_5 = OpTypePointer Function %_arr_v2float_uint_5
+ %82 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_82 = OpTypePointer UniformConstant %82
+ %86 = OpTypeSampler
+%_ptr_UniformConstant_86 = OpTypePointer UniformConstant %86
+ %90 = OpTypeSampledImage %82
+ %v3float = OpTypeVector %float 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+ %178 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %182 = OpVariable %_ptr_Output_v4float Output
+ %4 = OpFunction %void None %3
+ %5 = OpLabel
+ %483 = OpVariable %_ptr_Function_v4float Function
+ %484 = OpVariable %_ptr_Function_int Function
+ %486 = OpVariable %_ptr_Function__arr_v2float_uint_5 Function
+ %179 = OpLoad %v4float %178
+ %493 = OpAccessChain %_ptr_Uniform_float %23 %int_0 %int_0 %uint_3
+ %494 = OpLoad %float %493
+ OpStore %483 %43
+ OpStore %484 %int_0
+ OpBranch %495
+ %495 = OpLabel
+ OpLoopMerge %496 %497 None
+ OpBranch %498
+ %498 = OpLabel
+ %499 = OpLoad %int %484
+ %500 = OpSLessThan %bool %499 %int_5
+ OpBranchConditional %500 %501 %496
+ %501 = OpLabel
+ %504 = OpVectorShuffle %v2float %179 %179 0 1
+ %505 = OpLoad %int %484
+ %506 = OpAccessChain %_ptr_Uniform_v4float %23 %int_0 %int_2 %505
+ %507 = OpLoad %v4float %506
+ %508 = OpVectorShuffle %v2float %507 %507 0 1
+ %509 = OpFAdd %v2float %504 %508
+ %512 = OpAccessChain %_ptr_Uniform_v4float %23 %int_0 %int_1
+ %513 = OpLoad %v4float %512
+ %514 = OpVectorShuffle %v2float %513 %513 0 1
+ %517 = OpVectorShuffle %v2float %513 %513 2 3
+ %518 = OpExtInst %v2float %1 FClamp %509 %514 %517
+ %519 = OpAccessChain %_ptr_Function_v2float %486 %505
+ OpStore %519 %518
+ OpBranch %497
+ %497 = OpLabel
+ %520 = OpLoad %int %484
+ %521 = OpIAdd %int %520 %int_1
+ OpStore %484 %521
+ OpBranch %495
+ %496 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<CommonUniformElimPass>(text, true);
+}
+
+TEST_F(CommonUniformElimTest, LoadPlacedAfterPhi) {
+ const std::string text = R"(
+; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Uniform
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpPhi
+; CHECK-NEXT: OpLoad {{%\w+}} [[var]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpMemberDecorate %_struct_3 0 Offset 0
+ OpDecorate %_struct_3 Block
+ OpDecorate %4 DescriptorSet 0
+ OpDecorate %4 Binding 0
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %bool = OpTypeBool
+ %false = OpConstantFalse %bool
+ %uint = OpTypeInt 32 0
+ %v2uint = OpTypeVector %uint 2
+ %_struct_3 = OpTypeStruct %v2uint
+%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
+ %4 = OpVariable %_ptr_Uniform__struct_3 Uniform
+ %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+ %uint_2 = OpConstant %uint 2
+ %2 = OpFunction %void None %6
+ %15 = OpLabel
+ OpSelectionMerge %16 None
+ OpBranchConditional %false %17 %16
+ %17 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %18 = OpPhi %bool %false %15 %false %17
+ OpSelectionMerge %19 None
+ OpBranchConditional %false %20 %21
+ %20 = OpLabel
+ %22 = OpAccessChain %_ptr_Uniform_uint %4 %uint_0 %uint_0
+ %23 = OpLoad %uint %22
+ OpBranch %19
+ %21 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<CommonUniformElimPass>(text, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// Disqualifying cases: extensions, decorations, non-logical addressing,
+// non-structured control flow
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/compact_ids_test.cpp b/test/opt/compact_ids_test.cpp
new file mode 100644
index 0000000..b1e4b2c
--- /dev/null
+++ b/test/opt/compact_ids_test.cpp
@@ -0,0 +1,279 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#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 CompactIdsTest = PassTest<::testing::Test>;
+
+TEST_F(CompactIdsTest, PassOff) {
+ const std::string before =
+ R"(OpCapability Addresses
+OpCapability Kernel
+OpCapability GenericPointer
+OpCapability Linkage
+OpMemoryModel Physical32 OpenCL
+%99 = OpTypeInt 32 0
+%10 = OpTypeVector %99 2
+%20 = OpConstant %99 2
+%30 = OpTypeArray %99 %20
+)";
+
+ const std::string after = before;
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<NullPass>(before, after, false, false);
+}
+
+TEST_F(CompactIdsTest, PassOn) {
+ const std::string before =
+ R"(OpCapability Addresses
+OpCapability Kernel
+OpCapability GenericPointer
+OpCapability Linkage
+OpMemoryModel Physical32 OpenCL
+OpEntryPoint Kernel %3 "simple_kernel"
+%99 = OpTypeInt 32 0
+%10 = OpTypeVector %99 2
+%20 = OpConstant %99 2
+%30 = OpTypeArray %99 %20
+%40 = OpTypeVoid
+%50 = OpTypeFunction %40
+ %3 = OpFunction %40 None %50
+%70 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Addresses
+OpCapability Kernel
+OpCapability GenericPointer
+OpCapability Linkage
+OpMemoryModel Physical32 OpenCL
+OpEntryPoint Kernel %1 "simple_kernel"
+%2 = OpTypeInt 32 0
+%3 = OpTypeVector %2 2
+%4 = OpConstant %2 2
+%5 = OpTypeArray %2 %4
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%1 = OpFunction %6 None %7
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<CompactIdsPass>(before, after, false, false);
+}
+
+TEST(CompactIds, InstructionResultIsUpdated) {
+ // For https://github.com/KhronosGroup/SPIRV-Tools/issues/827
+ // In that bug, the compact Ids pass was directly updating the result Id
+ // word for an OpFunction instruction, but not updating the cached
+ // result_id_ in that Instruction object.
+ //
+ // This test is a bit cheesy. We don't expose internal interfaces enough
+ // to see the inconsistency. So reproduce the original scenario, with
+ // compact ids followed by a pass that trips up on the inconsistency.
+
+ const std::string input(R"(OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %100 "main"
+%200 = OpTypeVoid
+%300 = OpTypeFunction %200
+%100 = OpFunction %200 None %300
+%400 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+ std::vector<uint32_t> binary;
+ const spv_target_env env = SPV_ENV_UNIVERSAL_1_0;
+ spvtools::SpirvTools tools(env);
+ auto assembled = tools.Assemble(
+ input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_TRUE(assembled);
+
+ spvtools::Optimizer optimizer(env);
+ optimizer.RegisterPass(CreateCompactIdsPass());
+ // The exhaustive inliner will use the result_id
+ optimizer.RegisterPass(CreateInlineExhaustivePass());
+
+ // This should not crash!
+ optimizer.Run(binary.data(), binary.size(), &binary);
+
+ std::string disassembly;
+ tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ const std::string expected(R"(OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %1 "main"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+ EXPECT_THAT(disassembly, ::testing::Eq(expected));
+}
+
+TEST(CompactIds, HeaderIsUpdated) {
+ const std::string input(R"(OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %100 "main"
+%200 = OpTypeVoid
+%300 = OpTypeFunction %200
+%100 = OpFunction %200 None %300
+%400 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+ std::vector<uint32_t> binary;
+ const spv_target_env env = SPV_ENV_UNIVERSAL_1_0;
+ spvtools::SpirvTools tools(env);
+ auto assembled = tools.Assemble(
+ input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_TRUE(assembled);
+
+ spvtools::Optimizer optimizer(env);
+ optimizer.RegisterPass(CreateCompactIdsPass());
+ // The exhaustive inliner will use the result_id
+ optimizer.RegisterPass(CreateInlineExhaustivePass());
+
+ // This should not crash!
+ optimizer.Run(binary.data(), binary.size(), &binary);
+
+ std::string disassembly;
+ tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NONE);
+
+ const std::string expected(R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos SPIR-V Tools Assembler; 0
+; Bound: 5
+; Schema: 0
+OpCapability Shader
+OpMemoryModel Logical Simple
+OpEntryPoint GLCompute %1 "main"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+
+ EXPECT_THAT(disassembly, ::testing::Eq(expected));
+}
+
+// Test context consistency check after invalidating
+// CFG and others by compact IDs Pass.
+// Uses a GLSL shader with named labels for variety
+TEST(CompactIds, ConsistentCheck) {
+ const std::string input(R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_A %out_var_SV_TARGET
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %main "main"
+OpName %in_var_A "in.var.A"
+OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+OpDecorate %in_var_A Location 0
+OpDecorate %out_var_SV_TARGET Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%in_var_A = OpVariable %_ptr_Input_v4float Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %3
+%5 = OpLabel
+%12 = OpLoad %v4float %in_var_A
+%23 = OpVectorShuffle %v4float %12 %12 0 0 0 1
+OpStore %out_var_SV_TARGET %23
+OpReturn
+OpFunctionEnd
+)");
+
+ spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(context, nullptr);
+
+ CompactIdsPass compact_id_pass;
+ context->BuildInvalidAnalyses(compact_id_pass.GetPreservedAnalyses());
+ const auto status = compact_id_pass.Run(context.get());
+ ASSERT_NE(status, Pass::Status::Failure);
+ EXPECT_TRUE(context->IsConsistent());
+
+ // Test output just in case
+ std::vector<uint32_t> binary;
+ context->module()->ToBinary(&binary, false);
+ std::string disassembly;
+ tools.Disassemble(binary, &disassembly,
+ SpirvTools::kDefaultDisassembleOption);
+
+ const std::string expected(R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_A %out_var_SV_TARGET
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %main "main"
+OpName %in_var_A "in.var.A"
+OpName %out_var_SV_TARGET "out.var.SV_TARGET"
+OpDecorate %in_var_A Location 0
+OpDecorate %out_var_SV_TARGET Location 0
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%in_var_A = OpVariable %_ptr_Input_v4float Input
+%out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %5
+%10 = OpLabel
+%11 = OpLoad %v4float %in_var_A
+%12 = OpVectorShuffle %v4float %11 %11 0 0 0 1
+OpStore %out_var_SV_TARGET %12
+OpReturn
+OpFunctionEnd
+)");
+
+ EXPECT_THAT(disassembly, ::testing::Eq(expected));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/constant_manager_test.cpp b/test/opt/constant_manager_test.cpp
new file mode 100644
index 0000000..57dea65
--- /dev/null
+++ b/test/opt/constant_manager_test.cpp
@@ -0,0 +1,88 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/constants.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+namespace {
+
+using ConstantManagerTest = ::testing::Test;
+
+TEST_F(ConstantManagerTest, GetDefiningInstruction) {
+ const std::string text = R"(
+%int = OpTypeInt 32 0
+%1 = OpTypeStruct %int
+%2 = OpTypeStruct %int
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(context, nullptr);
+
+ Type* struct_type_1 = context->get_type_mgr()->GetType(1);
+ StructConstant struct_const_1(struct_type_1->AsStruct());
+ Instruction* const_inst_1 =
+ context->get_constant_mgr()->GetDefiningInstruction(&struct_const_1, 1);
+ EXPECT_EQ(const_inst_1->type_id(), 1);
+
+ Type* struct_type_2 = context->get_type_mgr()->GetType(2);
+ StructConstant struct_const_2(struct_type_2->AsStruct());
+ Instruction* const_inst_2 =
+ context->get_constant_mgr()->GetDefiningInstruction(&struct_const_2, 2);
+ EXPECT_EQ(const_inst_2->type_id(), 2);
+}
+
+TEST_F(ConstantManagerTest, GetDefiningInstruction2) {
+ const std::string text = R"(
+%int = OpTypeInt 32 0
+%1 = OpTypeStruct %int
+%2 = OpTypeStruct %int
+%3 = OpConstantNull %1
+%4 = OpConstantNull %2
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(context, nullptr);
+
+ Type* struct_type_1 = context->get_type_mgr()->GetType(1);
+ NullConstant struct_const_1(struct_type_1->AsStruct());
+ Instruction* const_inst_1 =
+ context->get_constant_mgr()->GetDefiningInstruction(&struct_const_1, 1);
+ EXPECT_EQ(const_inst_1->type_id(), 1);
+ EXPECT_EQ(const_inst_1->result_id(), 3);
+
+ Type* struct_type_2 = context->get_type_mgr()->GetType(2);
+ NullConstant struct_const_2(struct_type_2->AsStruct());
+ Instruction* const_inst_2 =
+ context->get_constant_mgr()->GetDefiningInstruction(&struct_const_2, 2);
+ EXPECT_EQ(const_inst_2->type_id(), 2);
+ EXPECT_EQ(const_inst_2->result_id(), 4);
+}
+
+} // namespace
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/copy_prop_array_test.cpp b/test/opt/copy_prop_array_test.cpp
new file mode 100644
index 0000000..504ae67
--- /dev/null
+++ b/test/opt/copy_prop_array_test.cpp
@@ -0,0 +1,1576 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 <iostream>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CopyPropArrayPassTest = PassTest<::testing::Test>;
+
+TEST_F(CopyPropArrayPassTest, BasicPropagateArray) {
+ const std::string before =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK: OpVariable
+; CHECK: OpAccessChain
+; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+; CHECK: [[element_ptr:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[new_address]] %24
+; CHECK: [[load:%\w+]] = OpLoad %v4float [[element_ptr]]
+; CHECK: OpStore %out_var_SV_Target [[load]]
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%28 = OpCompositeExtract %v4float %26 1
+%29 = OpCompositeExtract %v4float %26 2
+%30 = OpCompositeExtract %v4float %26 3
+%31 = OpCompositeExtract %v4float %26 4
+%32 = OpCompositeExtract %v4float %26 5
+%33 = OpCompositeExtract %v4float %26 6
+%34 = OpCompositeExtract %v4float %26 7
+%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
+OpStore %23 %35
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
+}
+
+TEST_F(CopyPropArrayPassTest, BasicPropagateArrayWithName) {
+ const std::string before =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %local "local"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK: OpVariable
+; CHECK: OpAccessChain
+; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+; CHECK: [[element_ptr:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[new_address]] %24
+; CHECK: [[load:%\w+]] = OpLoad %v4float [[element_ptr]]
+; CHECK: OpStore %out_var_SV_Target [[load]]
+%main = OpFunction %void None %13
+%22 = OpLabel
+%local = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%28 = OpCompositeExtract %v4float %26 1
+%29 = OpCompositeExtract %v4float %26 2
+%30 = OpCompositeExtract %v4float %26 3
+%31 = OpCompositeExtract %v4float %26 4
+%32 = OpCompositeExtract %v4float %26 5
+%33 = OpCompositeExtract %v4float %26 6
+%34 = OpCompositeExtract %v4float %26 7
+%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
+OpStore %local %35
+%36 = OpAccessChain %_ptr_Function_v4float %local %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
+}
+
+// Propagate 2d array. This test identifying a copy through multiple levels.
+// Also has to traverse multiple OpAccessChains.
+TEST_F(CopyPropArrayPassTest, Propagate2DArray) {
+ const std::string text =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_2 ArrayStride 16
+OpDecorate %_arr__arr_v4float_uint_2_uint_2 ArrayStride 32
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_2 = OpConstant %uint 2
+%_arr_v4float_uint_2 = OpTypeArray %v4float %uint_2
+%_arr__arr_v4float_uint_2_uint_2 = OpTypeArray %_arr_v4float_uint_2 %uint_2
+%type_MyCBuffer = OpTypeStruct %_arr__arr_v4float_uint_2_uint_2
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%14 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_2_0 = OpTypeArray %v4float %uint_2
+%_arr__arr_v4float_uint_2_0_uint_2 = OpTypeArray %_arr_v4float_uint_2_0 %uint_2
+%_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 = OpTypePointer Function %_arr__arr_v4float_uint_2_0_uint_2
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_v4float_uint_2_uint_2
+%_ptr_Function__arr_v4float_uint_2_0 = OpTypePointer Function %_arr_v4float_uint_2_0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK: OpVariable
+; CHECK: OpVariable
+; CHECK: OpAccessChain
+; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0
+%main = OpFunction %void None %14
+%25 = OpLabel
+%26 = OpVariable %_ptr_Function__arr_v4float_uint_2_0 Function
+%27 = OpVariable %_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 Function
+%28 = OpLoad %int %in_var_INDEX
+%29 = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0
+%30 = OpLoad %_arr__arr_v4float_uint_2_uint_2 %29
+%31 = OpCompositeExtract %_arr_v4float_uint_2 %30 0
+%32 = OpCompositeExtract %v4float %31 0
+%33 = OpCompositeExtract %v4float %31 1
+%34 = OpCompositeConstruct %_arr_v4float_uint_2_0 %32 %33
+%35 = OpCompositeExtract %_arr_v4float_uint_2 %30 1
+%36 = OpCompositeExtract %v4float %35 0
+%37 = OpCompositeExtract %v4float %35 1
+%38 = OpCompositeConstruct %_arr_v4float_uint_2_0 %36 %37
+%39 = OpCompositeConstruct %_arr__arr_v4float_uint_2_0_uint_2 %34 %38
+; CHECK: OpStore
+OpStore %27 %39
+%40 = OpAccessChain %_ptr_Function__arr_v4float_uint_2_0 %27 %28
+%42 = OpAccessChain %_ptr_Function_v4float %40 %28
+%43 = OpLoad %v4float %42
+; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_2 [[new_address]] %28
+; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[ac1]] %28
+; CHECK: [[load:%\w+]] = OpLoad %v4float [[ac2]]
+; CHECK: OpStore %out_var_SV_Target [[load]]
+OpStore %out_var_SV_Target %43
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(text, false);
+}
+
+// Propagate 2d array. This test identifying a copy through multiple levels.
+// Also has to traverse multiple OpAccessChains.
+TEST_F(CopyPropArrayPassTest, Propagate2DArrayWithMultiLevelExtract) {
+ const std::string text =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_2 ArrayStride 16
+OpDecorate %_arr__arr_v4float_uint_2_uint_2 ArrayStride 32
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_2 = OpConstant %uint 2
+%_arr_v4float_uint_2 = OpTypeArray %v4float %uint_2
+%_arr__arr_v4float_uint_2_uint_2 = OpTypeArray %_arr_v4float_uint_2 %uint_2
+%type_MyCBuffer = OpTypeStruct %_arr__arr_v4float_uint_2_uint_2
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%14 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_2_0 = OpTypeArray %v4float %uint_2
+%_arr__arr_v4float_uint_2_0_uint_2 = OpTypeArray %_arr_v4float_uint_2_0 %uint_2
+%_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 = OpTypePointer Function %_arr__arr_v4float_uint_2_0_uint_2
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_v4float_uint_2_uint_2
+%_ptr_Function__arr_v4float_uint_2_0 = OpTypePointer Function %_arr_v4float_uint_2_0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK: OpVariable
+; CHECK: OpVariable
+; CHECK: OpAccessChain
+; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0
+%main = OpFunction %void None %14
+%25 = OpLabel
+%26 = OpVariable %_ptr_Function__arr_v4float_uint_2_0 Function
+%27 = OpVariable %_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 Function
+%28 = OpLoad %int %in_var_INDEX
+%29 = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0
+%30 = OpLoad %_arr__arr_v4float_uint_2_uint_2 %29
+%32 = OpCompositeExtract %v4float %30 0 0
+%33 = OpCompositeExtract %v4float %30 0 1
+%34 = OpCompositeConstruct %_arr_v4float_uint_2_0 %32 %33
+%36 = OpCompositeExtract %v4float %30 1 0
+%37 = OpCompositeExtract %v4float %30 1 1
+%38 = OpCompositeConstruct %_arr_v4float_uint_2_0 %36 %37
+%39 = OpCompositeConstruct %_arr__arr_v4float_uint_2_0_uint_2 %34 %38
+; CHECK: OpStore
+OpStore %27 %39
+%40 = OpAccessChain %_ptr_Function__arr_v4float_uint_2_0 %27 %28
+%42 = OpAccessChain %_ptr_Function_v4float %40 %28
+%43 = OpLoad %v4float %42
+; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_2 [[new_address]] %28
+; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[ac1]] %28
+; CHECK: [[load:%\w+]] = OpLoad %v4float [[ac2]]
+; CHECK: OpStore %out_var_SV_Target [[load]]
+OpStore %out_var_SV_Target %43
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(text, false);
+}
+
+// Test decomposing an object when we need to "rewrite" a store.
+TEST_F(CopyPropArrayPassTest, DecomposeObjectForArrayStore) {
+ const std::string text =
+ R"( OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 600
+ OpName %type_MyCBuffer "type.MyCBuffer"
+ OpMemberName %type_MyCBuffer 0 "Data"
+ OpName %MyCBuffer "MyCBuffer"
+ OpName %main "main"
+ OpName %in_var_INDEX "in.var.INDEX"
+ OpName %out_var_SV_Target "out.var.SV_Target"
+ OpDecorate %_arr_v4float_uint_2 ArrayStride 16
+ OpDecorate %_arr__arr_v4float_uint_2_uint_2 ArrayStride 32
+ OpMemberDecorate %type_MyCBuffer 0 Offset 0
+ OpDecorate %type_MyCBuffer Block
+ OpDecorate %in_var_INDEX Flat
+ OpDecorate %in_var_INDEX Location 0
+ OpDecorate %out_var_SV_Target Location 0
+ OpDecorate %MyCBuffer DescriptorSet 0
+ OpDecorate %MyCBuffer Binding 0
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+%_arr_v4float_uint_2 = OpTypeArray %v4float %uint_2
+%_arr__arr_v4float_uint_2_uint_2 = OpTypeArray %_arr_v4float_uint_2 %uint_2
+%type_MyCBuffer = OpTypeStruct %_arr__arr_v4float_uint_2_uint_2
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+ %void = OpTypeVoid
+ %14 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_2_0 = OpTypeArray %v4float %uint_2
+%_arr__arr_v4float_uint_2_0_uint_2 = OpTypeArray %_arr_v4float_uint_2_0 %uint_2
+%_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 = OpTypePointer Function %_arr__arr_v4float_uint_2_0_uint_2
+ %int_0 = OpConstant %int 0
+%_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_v4float_uint_2_uint_2
+%_ptr_Function__arr_v4float_uint_2_0 = OpTypePointer Function %_arr_v4float_uint_2_0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+ %main = OpFunction %void None %14
+ %25 = OpLabel
+ %26 = OpVariable %_ptr_Function__arr_v4float_uint_2_0 Function
+ %27 = OpVariable %_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 Function
+ %28 = OpLoad %int %in_var_INDEX
+ %29 = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0
+ %30 = OpLoad %_arr__arr_v4float_uint_2_uint_2 %29
+ %31 = OpCompositeExtract %_arr_v4float_uint_2 %30 0
+ %32 = OpCompositeExtract %v4float %31 0
+ %33 = OpCompositeExtract %v4float %31 1
+ %34 = OpCompositeConstruct %_arr_v4float_uint_2_0 %32 %33
+ %35 = OpCompositeExtract %_arr_v4float_uint_2 %30 1
+ %36 = OpCompositeExtract %v4float %35 0
+ %37 = OpCompositeExtract %v4float %35 1
+ %38 = OpCompositeConstruct %_arr_v4float_uint_2_0 %36 %37
+ %39 = OpCompositeConstruct %_arr__arr_v4float_uint_2_0_uint_2 %34 %38
+ OpStore %27 %39
+; CHECK: [[access_chain:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_2
+ %40 = OpAccessChain %_ptr_Function__arr_v4float_uint_2_0 %27 %28
+; CHECK: [[load:%\w+]] = OpLoad %_arr_v4float_uint_2 [[access_chain]]
+ %41 = OpLoad %_arr_v4float_uint_2_0 %40
+; CHECK: [[extract1:%\w+]] = OpCompositeExtract %v4float [[load]] 0
+; CHECK: [[extract2:%\w+]] = OpCompositeExtract %v4float [[load]] 1
+; CHECK: [[construct:%\w+]] = OpCompositeConstruct %_arr_v4float_uint_2_0 [[extract1]] [[extract2]]
+; CHECK: OpStore %26 [[construct]]
+ OpStore %26 %41
+ %42 = OpAccessChain %_ptr_Function_v4float %26 %28
+ %43 = OpLoad %v4float %42
+ OpStore %out_var_SV_Target %43
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(text, false);
+}
+
+// Test decomposing an object when we need to "rewrite" a store.
+TEST_F(CopyPropArrayPassTest, DecomposeObjectForStructStore) {
+ const std::string text =
+ R"( OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+ OpExecutionMode %main OriginUpperLeft
+ OpSource HLSL 600
+ OpName %type_MyCBuffer "type.MyCBuffer"
+ OpMemberName %type_MyCBuffer 0 "Data"
+ OpName %MyCBuffer "MyCBuffer"
+ OpName %main "main"
+ OpName %in_var_INDEX "in.var.INDEX"
+ OpName %out_var_SV_Target "out.var.SV_Target"
+ OpMemberDecorate %type_MyCBuffer 0 Offset 0
+ OpDecorate %type_MyCBuffer Block
+ OpDecorate %in_var_INDEX Flat
+ OpDecorate %in_var_INDEX Location 0
+ OpDecorate %out_var_SV_Target Location 0
+ OpDecorate %MyCBuffer DescriptorSet 0
+ OpDecorate %MyCBuffer Binding 0
+; CHECK: OpDecorate [[decorated_type:%\w+]] GLSLPacked
+ OpDecorate %struct GLSLPacked
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+; CHECK: [[decorated_type]] = OpTypeStruct
+%struct = OpTypeStruct %float %uint
+%_arr_struct_uint_2 = OpTypeArray %struct %uint_2
+%type_MyCBuffer = OpTypeStruct %_arr_struct_uint_2
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+ %void = OpTypeVoid
+ %14 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+; CHECK: [[struct:%\w+]] = OpTypeStruct %float %uint
+%struct_0 = OpTypeStruct %float %uint
+%_arr_struct_0_uint_2 = OpTypeArray %struct_0 %uint_2
+%_ptr_Function__arr_struct_0_uint_2 = OpTypePointer Function %_arr_struct_0_uint_2
+ %int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_struct_uint_2 = OpTypePointer Uniform %_arr_struct_uint_2
+; CHECK: [[decorated_ptr:%\w+]] = OpTypePointer Uniform [[decorated_type]]
+%_ptr_Function_struct_0 = OpTypePointer Function %struct_0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+ %main = OpFunction %void None %14
+ %25 = OpLabel
+ %26 = OpVariable %_ptr_Function_struct_0 Function
+ %27 = OpVariable %_ptr_Function__arr_struct_0_uint_2 Function
+ %28 = OpLoad %int %in_var_INDEX
+ %29 = OpAccessChain %_ptr_Uniform__arr_struct_uint_2 %MyCBuffer %int_0
+ %30 = OpLoad %_arr_struct_uint_2 %29
+ %31 = OpCompositeExtract %struct %30 0
+ %32 = OpCompositeExtract %v4float %31 0
+ %33 = OpCompositeExtract %v4float %31 1
+ %34 = OpCompositeConstruct %struct_0 %32 %33
+ %35 = OpCompositeExtract %struct %30 1
+ %36 = OpCompositeExtract %float %35 0
+ %37 = OpCompositeExtract %uint %35 1
+ %38 = OpCompositeConstruct %struct_0 %36 %37
+ %39 = OpCompositeConstruct %_arr_struct_0_uint_2 %34 %38
+ OpStore %27 %39
+; CHECK: [[access_chain:%\w+]] = OpAccessChain [[decorated_ptr]]
+ %40 = OpAccessChain %_ptr_Function_struct_0 %27 %28
+; CHECK: [[load:%\w+]] = OpLoad [[decorated_type]] [[access_chain]]
+ %41 = OpLoad %struct_0 %40
+; CHECK: [[extract1:%\w+]] = OpCompositeExtract %float [[load]] 0
+; CHECK: [[extract2:%\w+]] = OpCompositeExtract %uint [[load]] 1
+; CHECK: [[construct:%\w+]] = OpCompositeConstruct [[struct]] [[extract1]] [[extract2]]
+; CHECK: OpStore %26 [[construct]]
+ OpStore %26 %41
+ %42 = OpAccessChain %_ptr_Function_v4float %26 %28
+ %43 = OpLoad %v4float %42
+ OpStore %out_var_SV_Target %43
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(text, false);
+}
+
+TEST_F(CopyPropArrayPassTest, CopyViaInserts) {
+ const std::string before =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK: OpVariable
+; CHECK: OpAccessChain
+; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+; CHECK: [[element_ptr:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[new_address]] %24
+; CHECK: [[load:%\w+]] = OpLoad %v4float [[element_ptr]]
+; CHECK: OpStore %out_var_SV_Target [[load]]
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%undef = OpUndef %_arr_v4float_uint_8_0
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%i0 = OpCompositeInsert %_arr_v4float_uint_8_0 %27 %undef 0
+%28 = OpCompositeExtract %v4float %26 1
+%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %i0 1
+%29 = OpCompositeExtract %v4float %26 2
+%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 2
+%30 = OpCompositeExtract %v4float %26 3
+%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3
+%31 = OpCompositeExtract %v4float %26 4
+%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4
+%32 = OpCompositeExtract %v4float %26 5
+%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5
+%33 = OpCompositeExtract %v4float %26 6
+%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6
+%34 = OpCompositeExtract %v4float %26 7
+%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7
+OpStore %23 %i7
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
+}
+
+TEST_F(CopyPropArrayPassTest, IsomorphicTypes1) {
+ const std::string before =
+ R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[s1:%\w+]] = OpTypeStruct [[int]]
+; CHECK: [[s2:%\w+]] = OpTypeStruct [[s1]]
+; CHECK: [[a1:%\w+]] = OpTypeArray [[s2]]
+; CHECK: [[s3:%\w+]] = OpTypeStruct [[a1]]
+; CHECK: [[p_s3:%\w+]] = OpTypePointer Uniform [[s3]]
+; CHECK: [[global_var:%\w+]] = OpVariable [[p_s3]] Uniform
+; CHECK: [[p_a1:%\w+]] = OpTypePointer Uniform [[a1]]
+; CHECK: [[p_s2:%\w+]] = OpTypePointer Uniform [[s2]]
+; CHECK: [[ac1:%\w+]] = OpAccessChain [[p_a1]] [[global_var]] %uint_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain [[p_s2]] [[ac1]] %uint_0
+; CHECK: [[ld:%\w+]] = OpLoad [[s2]] [[ac2]]
+; CHECK: [[ex:%\w+]] = OpCompositeExtract [[s1]] [[ld]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "PS_main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource HLSL 600
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 101
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %s1 = OpTypeStruct %uint
+ %s2 = OpTypeStruct %s1
+%a1 = OpTypeArray %s2 %uint_1
+ %s3 = OpTypeStruct %a1
+ %s1_1 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+ %void = OpTypeVoid
+ %13 = OpTypeFunction %void
+ %uint_0 = OpConstant %uint 0
+ %s1_0 = OpTypeStruct %uint
+ %s2_0 = OpTypeStruct %s1_0
+%a1_0 = OpTypeArray %s2_0 %uint_1
+ %s3_0 = OpTypeStruct %a1_0
+%p_s3 = OpTypePointer Uniform %s3
+%p_s3_0 = OpTypePointer Function %s3_0
+ %3 = OpVariable %p_s3 Uniform
+%p_a1_0 = OpTypePointer Function %a1_0
+%p_s2_0 = OpTypePointer Function %s2_0
+ %2 = OpFunction %void None %13
+ %20 = OpLabel
+ %21 = OpVariable %p_a1_0 Function
+ %22 = OpLoad %s3 %3
+ %23 = OpCompositeExtract %a1 %22 0
+ %24 = OpCompositeExtract %s2 %23 0
+ %25 = OpCompositeExtract %s1 %24 0
+ %26 = OpCompositeExtract %uint %25 0
+ %27 = OpCompositeConstruct %s1_0 %26
+ %32 = OpCompositeConstruct %s2_0 %27
+ %28 = OpCompositeConstruct %a1_0 %32
+ OpStore %21 %28
+ %29 = OpAccessChain %p_s2_0 %21 %uint_0
+ %30 = OpLoad %s2 %29
+ %31 = OpCompositeExtract %s1 %30 0
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
+}
+
+TEST_F(CopyPropArrayPassTest, IsomorphicTypes2) {
+ const std::string before =
+ R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[s1:%\w+]] = OpTypeStruct [[int]]
+; CHECK: [[s2:%\w+]] = OpTypeStruct [[s1]]
+; CHECK: [[a1:%\w+]] = OpTypeArray [[s2]]
+; CHECK: [[s3:%\w+]] = OpTypeStruct [[a1]]
+; CHECK: [[p_s3:%\w+]] = OpTypePointer Uniform [[s3]]
+; CHECK: [[global_var:%\w+]] = OpVariable [[p_s3]] Uniform
+; CHECK: [[p_s2:%\w+]] = OpTypePointer Uniform [[s2]]
+; CHECK: [[p_s1:%\w+]] = OpTypePointer Uniform [[s1]]
+; CHECK: [[ac1:%\w+]] = OpAccessChain [[p_s2]] [[global_var]] %uint_0 %uint_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain [[p_s1]] [[ac1]] %uint_0
+; CHECK: [[ld:%\w+]] = OpLoad [[s1]] [[ac2]]
+; CHECK: [[ex:%\w+]] = OpCompositeExtract [[int]] [[ld]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "PS_main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource HLSL 600
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 101
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %_struct_6 = OpTypeStruct %uint
+ %_struct_7 = OpTypeStruct %_struct_6
+%_arr__struct_7_uint_1 = OpTypeArray %_struct_7 %uint_1
+ %_struct_9 = OpTypeStruct %_arr__struct_7_uint_1
+ %_struct_10 = OpTypeStruct %uint
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+ %void = OpTypeVoid
+ %13 = OpTypeFunction %void
+ %uint_0 = OpConstant %uint 0
+ %_struct_15 = OpTypeStruct %uint
+%_arr__struct_15_uint_1 = OpTypeArray %_struct_15 %uint_1
+%_ptr_Uniform__struct_9 = OpTypePointer Uniform %_struct_9
+%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
+ %3 = OpVariable %_ptr_Uniform__struct_9 Uniform
+%_ptr_Function__arr__struct_15_uint_1 = OpTypePointer Function %_arr__struct_15_uint_1
+ %2 = OpFunction %void None %13
+ %20 = OpLabel
+ %21 = OpVariable %_ptr_Function__arr__struct_15_uint_1 Function
+ %22 = OpLoad %_struct_9 %3
+ %23 = OpCompositeExtract %_arr__struct_7_uint_1 %22 0
+ %24 = OpCompositeExtract %_struct_7 %23 0
+ %25 = OpCompositeExtract %_struct_6 %24 0
+ %26 = OpCompositeExtract %uint %25 0
+ %27 = OpCompositeConstruct %_struct_15 %26
+ %28 = OpCompositeConstruct %_arr__struct_15_uint_1 %27
+ OpStore %21 %28
+ %29 = OpAccessChain %_ptr_Function__struct_15 %21 %uint_0
+ %30 = OpLoad %_struct_15 %29
+ %31 = OpCompositeExtract %uint %30 0
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
+}
+
+TEST_F(CopyPropArrayPassTest, IsomorphicTypes3) {
+ const std::string before =
+ R"(
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[s1:%\w+]] = OpTypeStruct [[int]]
+; CHECK: [[s2:%\w+]] = OpTypeStruct [[s1]]
+; CHECK: [[a1:%\w+]] = OpTypeArray [[s2]]
+; CHECK: [[s3:%\w+]] = OpTypeStruct [[a1]]
+; CHECK: [[s1_1:%\w+]] = OpTypeStruct [[int]]
+; CHECK: [[p_s3:%\w+]] = OpTypePointer Uniform [[s3]]
+; CHECK: [[p_s1_1:%\w+]] = OpTypePointer Function [[s1_1]]
+; CHECK: [[global_var:%\w+]] = OpVariable [[p_s3]] Uniform
+; CHECK: [[p_s2:%\w+]] = OpTypePointer Uniform [[s2]]
+; CHECK: [[p_s1:%\w+]] = OpTypePointer Uniform [[s1]]
+; CHECK: [[var:%\w+]] = OpVariable [[p_s1_1]] Function
+; CHECK: [[ac1:%\w+]] = OpAccessChain [[p_s2]] [[global_var]] %uint_0 %uint_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain [[p_s1]] [[ac1]] %uint_0
+; CHECK: [[ld:%\w+]] = OpLoad [[s1]] [[ac2]]
+; CHECK: [[ex:%\w+]] = OpCompositeExtract [[int]] [[ld]]
+; CHECK: [[copy:%\w+]] = OpCompositeConstruct [[s1_1]] [[ex]]
+; CHECK: OpStore [[var]] [[copy]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "PS_main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource HLSL 600
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 101
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %_struct_6 = OpTypeStruct %uint
+ %_struct_7 = OpTypeStruct %_struct_6
+%_arr__struct_7_uint_1 = OpTypeArray %_struct_7 %uint_1
+ %_struct_9 = OpTypeStruct %_arr__struct_7_uint_1
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+ %void = OpTypeVoid
+ %13 = OpTypeFunction %void
+ %uint_0 = OpConstant %uint 0
+ %_struct_15 = OpTypeStruct %uint
+ %_struct_10 = OpTypeStruct %uint
+%_arr__struct_15_uint_1 = OpTypeArray %_struct_15 %uint_1
+%_ptr_Uniform__struct_9 = OpTypePointer Uniform %_struct_9
+%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
+ %3 = OpVariable %_ptr_Uniform__struct_9 Uniform
+%_ptr_Function__arr__struct_15_uint_1 = OpTypePointer Function %_arr__struct_15_uint_1
+ %2 = OpFunction %void None %13
+ %20 = OpLabel
+ %21 = OpVariable %_ptr_Function__arr__struct_15_uint_1 Function
+ %var = OpVariable %_ptr_Function__struct_15 Function
+ %22 = OpLoad %_struct_9 %3
+ %23 = OpCompositeExtract %_arr__struct_7_uint_1 %22 0
+ %24 = OpCompositeExtract %_struct_7 %23 0
+ %25 = OpCompositeExtract %_struct_6 %24 0
+ %26 = OpCompositeExtract %uint %25 0
+ %27 = OpCompositeConstruct %_struct_15 %26
+ %28 = OpCompositeConstruct %_arr__struct_15_uint_1 %27
+ OpStore %21 %28
+ %29 = OpAccessChain %_ptr_Function__struct_15 %21 %uint_0
+ %30 = OpLoad %_struct_15 %29
+ OpStore %var %30
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<CopyPropagateArrays>(before, false);
+}
+
+TEST_F(CopyPropArrayPassTest, BadMergingTwoObjects) {
+ // The second element in the |OpCompositeConstruct| is from a different
+ // object.
+ const std::string text =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpName %type_ConstBuf "type.ConstBuf"
+OpMemberName %type_ConstBuf 0 "TexSizeU"
+OpMemberName %type_ConstBuf 1 "TexSizeV"
+OpName %ConstBuf "ConstBuf"
+OpName %main "main"
+OpMemberDecorate %type_ConstBuf 0 Offset 0
+OpMemberDecorate %type_ConstBuf 1 Offset 8
+OpDecorate %type_ConstBuf Block
+OpDecorate %ConstBuf DescriptorSet 0
+OpDecorate %ConstBuf Binding 2
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%type_ConstBuf = OpTypeStruct %v2float %v2float
+%_ptr_Uniform_type_ConstBuf = OpTypePointer Uniform %type_ConstBuf
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%int_0 = OpConstant %uint 0
+%uint_2 = OpConstant %uint 2
+%_arr_v2float_uint_2 = OpTypeArray %v2float %uint_2
+%_ptr_Function__arr_v2float_uint_2 = OpTypePointer Function %_arr_v2float_uint_2
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+%ConstBuf = OpVariable %_ptr_Uniform_type_ConstBuf Uniform
+%main = OpFunction %void None %9
+%24 = OpLabel
+%25 = OpVariable %_ptr_Function__arr_v2float_uint_2 Function
+%27 = OpAccessChain %_ptr_Uniform_v2float %ConstBuf %int_0
+%28 = OpLoad %v2float %27
+%29 = OpAccessChain %_ptr_Uniform_v2float %ConstBuf %int_0
+%30 = OpLoad %v2float %29
+%31 = OpFNegate %v2float %30
+%37 = OpCompositeConstruct %_arr_v2float_uint_2 %28 %31
+OpStore %25 %37
+OpReturn
+OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CopyPropArrayPassTest, SecondElementNotContained) {
+ // The second element in the |OpCompositeConstruct| is not a memory object.
+ // Make sure no change happends.
+ const std::string text =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpName %type_ConstBuf "type.ConstBuf"
+OpMemberName %type_ConstBuf 0 "TexSizeU"
+OpMemberName %type_ConstBuf 1 "TexSizeV"
+OpName %ConstBuf "ConstBuf"
+OpName %main "main"
+OpMemberDecorate %type_ConstBuf 0 Offset 0
+OpMemberDecorate %type_ConstBuf 1 Offset 8
+OpDecorate %type_ConstBuf Block
+OpDecorate %ConstBuf DescriptorSet 0
+OpDecorate %ConstBuf Binding 2
+OpDecorate %ConstBuf2 DescriptorSet 1
+OpDecorate %ConstBuf2 Binding 2
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%type_ConstBuf = OpTypeStruct %v2float %v2float
+%_ptr_Uniform_type_ConstBuf = OpTypePointer Uniform %type_ConstBuf
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%int_0 = OpConstant %uint 0
+%int_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%_arr_v2float_uint_2 = OpTypeArray %v2float %uint_2
+%_ptr_Function__arr_v2float_uint_2 = OpTypePointer Function %_arr_v2float_uint_2
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+%ConstBuf = OpVariable %_ptr_Uniform_type_ConstBuf Uniform
+%ConstBuf2 = OpVariable %_ptr_Uniform_type_ConstBuf Uniform
+%main = OpFunction %void None %9
+%24 = OpLabel
+%25 = OpVariable %_ptr_Function__arr_v2float_uint_2 Function
+%27 = OpAccessChain %_ptr_Uniform_v2float %ConstBuf %int_0
+%28 = OpLoad %v2float %27
+%29 = OpAccessChain %_ptr_Uniform_v2float %ConstBuf2 %int_1
+%30 = OpLoad %v2float %29
+%37 = OpCompositeConstruct %_arr_v2float_uint_2 %28 %30
+OpStore %25 %37
+OpReturn
+OpFunctionEnd
+)";
+
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+// This test will place a load before the store. We cannot propagate in this
+// case.
+TEST_F(CopyPropArrayPassTest, LoadBeforeStore) {
+ const std::string text =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%38 = OpAccessChain %_ptr_Function_v4float %23 %24
+%39 = OpLoad %v4float %36
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%28 = OpCompositeExtract %v4float %26 1
+%29 = OpCompositeExtract %v4float %26 2
+%30 = OpCompositeExtract %v4float %26 3
+%31 = OpCompositeExtract %v4float %26 4
+%32 = OpCompositeExtract %v4float %26 5
+%33 = OpCompositeExtract %v4float %26 6
+%34 = OpCompositeExtract %v4float %26 7
+%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
+OpStore %23 %35
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// This test will place a load where it is not dominated by the store. We
+// cannot propagate in this case.
+TEST_F(CopyPropArrayPassTest, LoadNotDominated) {
+ const std::string text =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+OpSelectionMerge %merge None
+OpBranchConditional %true %if %else
+%if = OpLabel
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%28 = OpCompositeExtract %v4float %26 1
+%29 = OpCompositeExtract %v4float %26 2
+%30 = OpCompositeExtract %v4float %26 3
+%31 = OpCompositeExtract %v4float %26 4
+%32 = OpCompositeExtract %v4float %26 5
+%33 = OpCompositeExtract %v4float %26 6
+%34 = OpCompositeExtract %v4float %26 7
+%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
+OpStore %23 %35
+%38 = OpAccessChain %_ptr_Function_v4float %23 %24
+%39 = OpLoad %v4float %36
+OpBranch %merge
+%else = OpLabel
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpBranch %merge
+%merge = OpLabel
+%phi = OpPhi %out_var_SV_Target %39 %if %37 %else
+OpStore %out_var_SV_Target %phi
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// This test has a partial store to the variable. We cannot propagate in this
+// case.
+TEST_F(CopyPropArrayPassTest, PartialStore) {
+ const std::string text =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%f0 = OpConstant %float 0
+%v4const = OpConstantComposite %v4float %f0 %f0 %f0 %f0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%28 = OpCompositeExtract %v4float %26 1
+%29 = OpCompositeExtract %v4float %26 2
+%30 = OpCompositeExtract %v4float %26 3
+%31 = OpCompositeExtract %v4float %26 4
+%32 = OpCompositeExtract %v4float %26 5
+%33 = OpCompositeExtract %v4float %26 6
+%34 = OpCompositeExtract %v4float %26 7
+%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
+OpStore %23 %35
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+%39 = OpStore %36 %v4const
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// This test does not have a proper copy of an object. We cannot propagate in
+// this case.
+TEST_F(CopyPropArrayPassTest, NotACopy) {
+ const std::string text =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%f0 = OpConstant %float 0
+%v4const = OpConstantComposite %v4float %f0 %f0 %f0 %f0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%28 = OpCompositeExtract %v4float %26 0
+%29 = OpCompositeExtract %v4float %26 2
+%30 = OpCompositeExtract %v4float %26 3
+%31 = OpCompositeExtract %v4float %26 4
+%32 = OpCompositeExtract %v4float %26 5
+%33 = OpCompositeExtract %v4float %26 6
+%34 = OpCompositeExtract %v4float %26 7
+%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
+OpStore %23 %35
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CopyPropArrayPassTest, BadCopyViaInserts1) {
+ const std::string text =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%undef = OpUndef %_arr_v4float_uint_8_0
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%i0 = OpCompositeInsert %_arr_v4float_uint_8_0 %27 %undef 0
+%28 = OpCompositeExtract %v4float %26 1
+%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %i0 1
+%29 = OpCompositeExtract %v4float %26 2
+%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 3
+%30 = OpCompositeExtract %v4float %26 3
+%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3
+%31 = OpCompositeExtract %v4float %26 4
+%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4
+%32 = OpCompositeExtract %v4float %26 5
+%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5
+%33 = OpCompositeExtract %v4float %26 6
+%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6
+%34 = OpCompositeExtract %v4float %26 7
+%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7
+OpStore %23 %i7
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CopyPropArrayPassTest, BadCopyViaInserts2) {
+ const std::string text =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%undef = OpUndef %_arr_v4float_uint_8_0
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%27 = OpCompositeExtract %v4float %26 0
+%i0 = OpCompositeInsert %_arr_v4float_uint_8_0 %27 %undef 0
+%28 = OpCompositeExtract %v4float %26 1
+%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %i0 1
+%29 = OpCompositeExtract %v4float %26 3
+%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 2
+%30 = OpCompositeExtract %v4float %26 3
+%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3
+%31 = OpCompositeExtract %v4float %26 4
+%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4
+%32 = OpCompositeExtract %v4float %26 5
+%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5
+%33 = OpCompositeExtract %v4float %26 6
+%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6
+%34 = OpCompositeExtract %v4float %26 7
+%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7
+OpStore %23 %i7
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CopyPropArrayPassTest, BadCopyViaInserts3) {
+ const std::string text =
+ R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %type_MyCBuffer "type.MyCBuffer"
+OpMemberName %type_MyCBuffer 0 "Data"
+OpName %MyCBuffer "MyCBuffer"
+OpName %main "main"
+OpName %in_var_INDEX "in.var.INDEX"
+OpName %out_var_SV_Target "out.var.SV_Target"
+OpDecorate %_arr_v4float_uint_8 ArrayStride 16
+OpMemberDecorate %type_MyCBuffer 0 Offset 0
+OpDecorate %type_MyCBuffer Block
+OpDecorate %in_var_INDEX Flat
+OpDecorate %in_var_INDEX Location 0
+OpDecorate %out_var_SV_Target Location 0
+OpDecorate %MyCBuffer DescriptorSet 0
+OpDecorate %MyCBuffer Binding 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_8 = OpConstant %uint 8
+%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
+%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
+%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
+%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
+%in_var_INDEX = OpVariable %_ptr_Input_int Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %13
+%22 = OpLabel
+%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
+%undef = OpUndef %_arr_v4float_uint_8_0
+%24 = OpLoad %int %in_var_INDEX
+%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
+%26 = OpLoad %_arr_v4float_uint_8 %25
+%28 = OpCompositeExtract %v4float %26 1
+%i1 = OpCompositeInsert %_arr_v4float_uint_8_0 %28 %undef 1
+%29 = OpCompositeExtract %v4float %26 2
+%i2 = OpCompositeInsert %_arr_v4float_uint_8_0 %29 %i1 2
+%30 = OpCompositeExtract %v4float %26 3
+%i3 = OpCompositeInsert %_arr_v4float_uint_8_0 %30 %i2 3
+%31 = OpCompositeExtract %v4float %26 4
+%i4 = OpCompositeInsert %_arr_v4float_uint_8_0 %31 %i3 4
+%32 = OpCompositeExtract %v4float %26 5
+%i5 = OpCompositeInsert %_arr_v4float_uint_8_0 %32 %i4 5
+%33 = OpCompositeExtract %v4float %26 6
+%i6 = OpCompositeInsert %_arr_v4float_uint_8_0 %33 %i5 6
+%34 = OpCompositeExtract %v4float %26 7
+%i7 = OpCompositeInsert %_arr_v4float_uint_8_0 %34 %i6 7
+OpStore %23 %i7
+%36 = OpAccessChain %_ptr_Function_v4float %23 %24
+%37 = OpLoad %v4float %36
+OpStore %out_var_SV_Target %37
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ auto result = SinglePassRunAndDisassemble<CopyPropagateArrays>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(CopyPropArrayPassTest, AtomicAdd) {
+ const std::string before = R"(OpCapability SampledBuffer
+OpCapability StorageImageExtendedFormats
+OpCapability ImageBuffer
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID
+OpExecutionMode %2 LocalSize 64 1 1
+OpSource HLSL 600
+OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+OpDecorate %4 DescriptorSet 4
+OpDecorate %4 Binding 70
+%uint = OpTypeInt 32 0
+%6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui
+%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6
+%_ptr_Function_6 = OpTypePointer Function %6
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%_ptr_Image_uint = OpTypePointer Image %uint
+%4 = OpVariable %_ptr_UniformConstant_6 UniformConstant
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%2 = OpFunction %void None %10
+%17 = OpLabel
+%16 = OpVariable %_ptr_Function_6 Function
+%18 = OpLoad %6 %4
+OpStore %16 %18
+%19 = OpImageTexelPointer %_ptr_Image_uint %16 %uint_0 %uint_0
+%20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after = R"(OpCapability SampledBuffer
+OpCapability StorageImageExtendedFormats
+OpCapability ImageBuffer
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID
+OpExecutionMode %2 LocalSize 64 1 1
+OpSource HLSL 600
+OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+OpDecorate %4 DescriptorSet 4
+OpDecorate %4 Binding 70
+%uint = OpTypeInt 32 0
+%6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui
+%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6
+%_ptr_Function_6 = OpTypePointer Function %6
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%_ptr_Image_uint = OpTypePointer Image %uint
+%4 = OpVariable %_ptr_UniformConstant_6 UniformConstant
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+%2 = OpFunction %void None %10
+%17 = OpLabel
+%16 = OpVariable %_ptr_Function_6 Function
+%18 = OpLoad %6 %4
+OpStore %16 %18
+%19 = OpImageTexelPointer %_ptr_Image_uint %4 %uint_0 %uint_0
+%20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<CopyPropagateArrays>(before, after, true, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dead_branch_elim_test.cpp b/test/opt/dead_branch_elim_test.cpp
new file mode 100644
index 0000000..66e3bdd
--- /dev/null
+++ b/test/opt/dead_branch_elim_test.cpp
@@ -0,0 +1,2878 @@
+// 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, 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 preceeding 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 merge of a surrounding loop, the selection merge, and another block
+ // inside the selection merge, then we must keep the OpSelectionMerge
+ // instruction on that branch.
+ 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]] 0 [[loop_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 0 %loop_merge 1 %bb3
+%bb3 = OpLabel
+OpBranch %sel_merge
+%bb4 = OpLabel
+OpBranch %sel_merge
+%sel_merge = OpLabel
+OpBranch %loop_merge
+%cont = OpLabel
+OpBranch %loop_header
+%loop_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
+}
+
+TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue3) {
+ // Checks that if a selection merge construct contains a conditional branch
+ // to the merge of a surrounding loop, the selection merge, and another block
+ // inside the selection merge, then we must keep the OpSelectionMerge
+ // instruction on that branch.
+ 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: 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: OpSwitch {{%\w+}} [[sel_merge]] 0 [[loop_continue]] 1 [[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
+OpSwitch %undef_int %sel_merge 0 %cont 1 %bb3
+%bb3 = OpLabel
+OpBranch %sel_merge
+%bb4 = OpLabel
+OpBranch %sel_merge
+%sel_merge = OpLabel
+OpBranch %loop_merge
+%cont = OpLabel
+OpBranch %loop_header
+%loop_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
+}
+
+TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoop4) {
+ // Same as |SelectionMergeWithExitToLoop|, except the branch in the selection
+ // construct is an |OpSwitch| instead of an |OpConditionalBranch|. The
+ // OpSelectionMerge instruction is not needed in this case either.
+ const std::string predefs = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+%void = OpTypeVoid
+%func_type = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%undef_int = OpUndef %uint
+)";
+
+ const std::string body =
+ R"(
+; CHECK: OpLoopMerge [[loop_merge:%\w+]]
+; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: OpBranch [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_merge]] 1 [[bb3:%\w+]]
+; CHECK: [[bb3]] = OpLabel
+; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
+; CHECK: [[sel_merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop_merge]]
+; CHECK: [[loop_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+%main = OpFunction %void None %func_type
+%entry_bb = OpLabel
+OpBranch %loop_header
+%loop_header = OpLabel
+OpLoopMerge %loop_merge %cont None
+OpBranch %bb1
+%bb1 = OpLabel
+OpSelectionMerge %sel_merge None
+OpBranchConditional %true %bb2 %bb4
+%bb2 = OpLabel
+OpSwitch %undef_int %bb3 0 %loop_merge 1 %bb3
+%bb3 = OpLabel
+OpBranch %sel_merge
+%bb4 = OpLabel
+OpBranch %sel_merge
+%sel_merge = OpLabel
+OpBranch %loop_merge
+%cont = OpLabel
+OpBranch %loop_header
+%loop_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
+}
+
+TEST_F(DeadBranchElimTest, SelectionMergeWithExitToLoopContinue4) {
+ // Same as |SelectionMergeWithExitToLoopContinue|, except the branch in the
+ // selection construct is an |OpSwitch| instead of an |OpConditionalBranch|.
+ // The OpSelectionMerge instruction is not needed in this case either.
+ const std::string predefs = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+%void = OpTypeVoid
+%func_type = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%undef_int = OpUndef %uint
+)";
+
+ const std::string body =
+ R"(
+; CHECK: OpLoopMerge [[loop_merge:%\w+]] [[loop_cont:%\w+]]
+; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: OpBranch [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSwitch {{%\w+}} [[bb3:%\w+]] 0 [[loop_cont]] 1 [[bb3:%\w+]]
+; CHECK: [[bb3]] = OpLabel
+; CHECK-NEXT: OpBranch [[sel_merge:%\w+]]
+; CHECK: [[sel_merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop_merge]]
+; CHECK: [[loop_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+%main = OpFunction %void None %func_type
+%entry_bb = OpLabel
+OpBranch %loop_header
+%loop_header = OpLabel
+OpLoopMerge %loop_merge %cont None
+OpBranch %bb1
+%bb1 = OpLabel
+OpSelectionMerge %sel_merge None
+OpBranchConditional %true %bb2 %bb4
+%bb2 = OpLabel
+OpSwitch %undef_int %bb3 0 %cont 1 %bb3
+%bb3 = OpLabel
+OpBranch %sel_merge
+%bb4 = OpLabel
+OpBranch %sel_merge
+%sel_merge = OpLabel
+OpBranch %loop_merge
+%cont = OpLabel
+OpBranch %loop_header
+%loop_merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
+}
+
+TEST_F(DeadBranchElimTest, 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
+)";
+
+ SinglePassRunAndMatch<DeadBranchElimPass>(predefs + body, true);
+}
+
+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);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// More complex control flow
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dead_insert_elim_test.cpp b/test/opt/dead_insert_elim_test.cpp
new file mode 100644
index 0000000..8ae6894
--- /dev/null
+++ b/test/opt/dead_insert_elim_test.cpp
@@ -0,0 +1,571 @@
+// 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 DeadInsertElimTest = PassTest<::testing::Test>;
+
+TEST_F(DeadInsertElimTest, InsertAfterInsertElim) {
+ // With two insertions to the same offset, the first is dead.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in float In0;
+ // layout (location=1) in float In1;
+ // layout (location=2) in vec2 In2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec2 v = In2;
+ // v.x = In0 + In1; // dead
+ // v.x = 0.0;
+ // OutColor = v.xyxy;
+ // }
+
+ const std::string before_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In2 "In2"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %OutColor "OutColor"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_n"
+OpName %_ ""
+OpDecorate %In2 Location 2
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %OutColor Location 0
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%In2 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In0 = OpVariable %_ptr_Input_float Input
+%In1 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%int = OpTypeInt 32 1
+%_Globals_ = OpTypeStruct %uint %int
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+)";
+
+ const std::string after_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In2 "In2"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %OutColor "OutColor"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_n"
+OpName %_ ""
+OpDecorate %In2 Location 2
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %OutColor Location 0
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%In2 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In0 = OpVariable %_ptr_Input_float Input
+%In1 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%int = OpTypeInt 32 1
+%_Globals_ = OpTypeStruct %uint %int
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %11
+%25 = OpLabel
+%26 = OpLoad %v2float %In2
+%27 = OpLoad %float %In0
+%28 = OpLoad %float %In1
+%29 = OpFAdd %float %27 %28
+%35 = OpCompositeInsert %v2float %29 %26 0
+%37 = OpCompositeInsert %v2float %float_0 %35 0
+%33 = OpVectorShuffle %v4float %37 %37 0 1 0 1
+OpStore %OutColor %33
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpLoad %v2float %In2
+%29 = OpCompositeInsert %v2float %float_0 %24 0
+%30 = OpVectorShuffle %v4float %29 %29 0 1 0 1
+OpStore %OutColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<DeadInsertElimPass>(before_predefs + before,
+ after_predefs + after, true, true);
+}
+
+TEST_F(DeadInsertElimTest, DeadInsertInChainWithPhi) {
+ // Dead insert eliminated with phi in insertion chain.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in vec4 In0;
+ // layout (location=1) in float In1;
+ // layout (location=2) in float In2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // layout(std140, binding = 0 ) uniform _Globals_
+ // {
+ // bool g_b;
+ // };
+ //
+ // void main()
+ // {
+ // vec4 v = In0;
+ // v.z = In1 + In2;
+ // if (g_b) v.w = 1.0;
+ // OutColor = vec4(v.x,v.y,0.0,v.w);
+ // }
+
+ const std::string before_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%_Globals_ = OpTypeStruct %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+)";
+
+ const std::string after_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%_Globals_ = OpTypeStruct %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %11
+%31 = OpLabel
+%32 = OpLoad %v4float %In0
+%33 = OpLoad %float %In1
+%34 = OpLoad %float %In2
+%35 = OpFAdd %float %33 %34
+%51 = OpCompositeInsert %v4float %35 %32 2
+%37 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%38 = OpLoad %uint %37
+%39 = OpINotEqual %bool %38 %uint_0
+OpSelectionMerge %40 None
+OpBranchConditional %39 %41 %40
+%41 = OpLabel
+%53 = OpCompositeInsert %v4float %float_1 %51 3
+OpBranch %40
+%40 = OpLabel
+%60 = OpPhi %v4float %51 %31 %53 %41
+%55 = OpCompositeExtract %float %60 0
+%57 = OpCompositeExtract %float %60 1
+%59 = OpCompositeExtract %float %60 3
+%49 = OpCompositeConstruct %v4float %55 %57 %float_0 %59
+OpStore %OutColor %49
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%27 = OpLabel
+%28 = OpLoad %v4float %In0
+%33 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%34 = OpLoad %uint %33
+%35 = OpINotEqual %bool %34 %uint_0
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %36
+%37 = OpLabel
+%38 = OpCompositeInsert %v4float %float_1 %28 3
+OpBranch %36
+%36 = OpLabel
+%39 = OpPhi %v4float %28 %27 %38 %37
+%40 = OpCompositeExtract %float %39 0
+%41 = OpCompositeExtract %float %39 1
+%42 = OpCompositeExtract %float %39 3
+%43 = OpCompositeConstruct %v4float %40 %41 %float_0 %42
+OpStore %OutColor %43
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<DeadInsertElimPass>(before_predefs + before,
+ after_predefs + after, true, true);
+}
+
+TEST_F(DeadInsertElimTest, DeadInsertTwoPasses) {
+ // Dead insert which requires two passes to eliminate
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in vec4 In0;
+ // layout (location=1) in float In1;
+ // layout (location=2) in float In2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // layout(std140, binding = 0 ) uniform _Globals_
+ // {
+ // bool g_b;
+ // bool g_b2;
+ // };
+ //
+ // void main()
+ // {
+ // vec4 v1, v2;
+ // v1 = In0;
+ // v1.y = In1 + In2; // dead, second pass
+ // if (g_b) v1.x = 1.0;
+ // v2.x = v1.x;
+ // v2.y = v1.y; // dead, first pass
+ // if (g_b2) v2.x = 0.0;
+ // OutColor = vec4(v2.x,v2.x,0.0,1.0);
+ // }
+
+ const std::string before_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_b2"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_Globals_ = OpTypeStruct %uint %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%float_0 = OpConstant %float 0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%27 = OpUndef %v4float
+)";
+
+ const std::string after_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_b2"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_Globals_ = OpTypeStruct %uint %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%float_0 = OpConstant %float 0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%27 = OpUndef %v4float
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%28 = OpLabel
+%29 = OpLoad %v4float %In0
+%30 = OpLoad %float %In1
+%31 = OpLoad %float %In2
+%32 = OpFAdd %float %30 %31
+%33 = OpCompositeInsert %v4float %32 %29 1
+%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%35 = OpLoad %uint %34
+%36 = OpINotEqual %bool %35 %uint_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
+%39 = OpCompositeInsert %v4float %float_1 %33 0
+OpBranch %37
+%37 = OpLabel
+%40 = OpPhi %v4float %33 %28 %39 %38
+%41 = OpCompositeExtract %float %40 0
+%42 = OpCompositeInsert %v4float %41 %27 0
+%43 = OpCompositeExtract %float %40 1
+%44 = OpCompositeInsert %v4float %43 %42 1
+%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1
+%46 = OpLoad %uint %45
+%47 = OpINotEqual %bool %46 %uint_0
+OpSelectionMerge %48 None
+OpBranchConditional %47 %49 %48
+%49 = OpLabel
+%50 = OpCompositeInsert %v4float %float_0 %44 0
+OpBranch %48
+%48 = OpLabel
+%51 = OpPhi %v4float %44 %37 %50 %49
+%52 = OpCompositeExtract %float %51 0
+%53 = OpCompositeExtract %float %51 0
+%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1
+OpStore %OutColor %54
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%28 = OpLabel
+%29 = OpLoad %v4float %In0
+%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%35 = OpLoad %uint %34
+%36 = OpINotEqual %bool %35 %uint_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
+%39 = OpCompositeInsert %v4float %float_1 %29 0
+OpBranch %37
+%37 = OpLabel
+%40 = OpPhi %v4float %29 %28 %39 %38
+%41 = OpCompositeExtract %float %40 0
+%42 = OpCompositeInsert %v4float %41 %27 0
+%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1
+%46 = OpLoad %uint %45
+%47 = OpINotEqual %bool %46 %uint_0
+OpSelectionMerge %48 None
+OpBranchConditional %47 %49 %48
+%49 = OpLabel
+%50 = OpCompositeInsert %v4float %float_0 %42 0
+OpBranch %48
+%48 = OpLabel
+%51 = OpPhi %v4float %42 %37 %50 %49
+%52 = OpCompositeExtract %float %51 0
+%53 = OpCompositeExtract %float %51 0
+%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1
+OpStore %OutColor %54
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<DeadInsertElimPass>(before_predefs + before,
+ after_predefs + after, true, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dead_variable_elim_test.cpp b/test/opt/dead_variable_elim_test.cpp
new file mode 100644
index 0000000..fca13a8
--- /dev/null
+++ b/test/opt/dead_variable_elim_test.cpp
@@ -0,0 +1,298 @@
+// 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 "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using DeadVariableElimTest = PassTest<::testing::Test>;
+
+// %dead is unused. Make sure we remove it along with its name.
+TEST_F(DeadVariableElimTest, RemoveUnreferenced) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%dead = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<DeadVariableElimination>(before, after, true, true);
+}
+
+// Since %dead is exported, make sure we keep it. It could be referenced
+// somewhere else.
+TEST_F(DeadVariableElimTest, KeepExported) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpDecorate %dead LinkageAttributes "dead" Export
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%dead = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<DeadVariableElimination>(before, before, true, true);
+}
+
+// Delete %dead because it is unreferenced. Then %initializer becomes
+// unreferenced, so remove it as well.
+TEST_F(DeadVariableElimTest, RemoveUnreferencedWithInit1) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%dead = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<DeadVariableElimination>(before, after, true, true);
+}
+
+// Delete %dead because it is unreferenced. In this case, the initialized has
+// another reference, and should not be removed.
+TEST_F(DeadVariableElimTest, RemoveUnreferencedWithInit2) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%dead = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %initializer
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %initializer
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<DeadVariableElimination>(before, after, true, true);
+}
+
+// Keep %live because it is used, and its initializer.
+TEST_F(DeadVariableElimTest, KeepReferenced) {
+ const std::string before =
+ R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %live "live"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%live = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %live
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<DeadVariableElimination>(before, before, true, true);
+}
+
+// This test that the decoration associated with a variable are removed when the
+// variable is removed.
+TEST_F(DeadVariableElimTest, RemoveVariableAndDecorations) {
+ const std::string before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %B "B"
+OpMemberName %B 0 "a"
+OpName %Bdat "Bdat"
+OpMemberDecorate %B 0 Offset 0
+OpDecorate %B BufferBlock
+OpDecorate %Bdat DescriptorSet 0
+OpDecorate %Bdat Binding 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%B = OpTypeStruct %uint
+%_ptr_Uniform_B = OpTypePointer Uniform %B
+%Bdat = OpVariable %_ptr_Uniform_B Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %B "B"
+OpMemberName %B 0 "a"
+OpMemberDecorate %B 0 Offset 0
+OpDecorate %B BufferBlock
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%B = OpTypeStruct %uint
+%_ptr_Uniform_B = OpTypePointer Uniform %B
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<DeadVariableElimination>(before, after, true, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/decoration_manager_test.cpp b/test/opt/decoration_manager_test.cpp
new file mode 100644
index 0000000..f85ff6a
--- /dev/null
+++ b/test/opt/decoration_manager_test.cpp
@@ -0,0 +1,1283 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// 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 <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/build_module.h"
+#include "source/opt/decoration_manager.h"
+#include "source/opt/ir_context.h"
+#include "source/spirv_constant.h"
+#include "test/unit_spirv.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+namespace {
+
+using spvtest::MakeVector;
+
+class DecorationManagerTest : public ::testing::Test {
+ public:
+ DecorationManagerTest()
+ : tools_(SPV_ENV_UNIVERSAL_1_2),
+ context_(),
+ consumer_([this](spv_message_level_t level, const char*,
+ const spv_position_t& position, const char* message) {
+ if (!error_message_.empty()) error_message_ += "\n";
+ switch (level) {
+ case SPV_MSG_FATAL:
+ case SPV_MSG_INTERNAL_ERROR:
+ case SPV_MSG_ERROR:
+ error_message_ += "ERROR";
+ break;
+ case SPV_MSG_WARNING:
+ error_message_ += "WARNING";
+ break;
+ case SPV_MSG_INFO:
+ error_message_ += "INFO";
+ break;
+ case SPV_MSG_DEBUG:
+ error_message_ += "DEBUG";
+ break;
+ }
+ error_message_ +=
+ ": " + std::to_string(position.index) + ": " + message;
+ }),
+ disassemble_options_(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER),
+ error_message_() {
+ tools_.SetMessageConsumer(consumer_);
+ }
+
+ void TearDown() override { error_message_.clear(); }
+
+ DecorationManager* GetDecorationManager(const std::string& text) {
+ context_ = BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer_, text);
+ if (context_.get())
+ return context_->get_decoration_mgr();
+ else
+ return nullptr;
+ }
+
+ // Disassembles |binary| and outputs the result in |text|. If |text| is a
+ // null pointer, SPV_ERROR_INVALID_POINTER is returned.
+ spv_result_t Disassemble(const std::vector<uint32_t>& binary,
+ std::string* text) {
+ if (!text) return SPV_ERROR_INVALID_POINTER;
+ return tools_.Disassemble(binary, text, disassemble_options_)
+ ? SPV_SUCCESS
+ : SPV_ERROR_INVALID_BINARY;
+ }
+
+ // Returns the accumulated error messages for the test.
+ std::string GetErrorMessage() const { return error_message_; }
+
+ std::string ToText(const std::vector<Instruction*>& inst) {
+ std::vector<uint32_t> binary = {SpvMagicNumber, 0x10200, 0u, 2u, 0u};
+ for (const Instruction* i : inst)
+ i->ToBinaryWithoutAttachedDebugInsts(&binary);
+ std::string text;
+ Disassemble(binary, &text);
+ return text;
+ }
+
+ std::string ModuleToText() {
+ std::vector<uint32_t> binary;
+ context_->module()->ToBinary(&binary, false);
+ std::string text;
+ Disassemble(binary, &text);
+ return text;
+ }
+
+ spvtools::MessageConsumer GetConsumer() { return consumer_; }
+
+ private:
+ // An instance for calling SPIRV-Tools functionalities.
+ spvtools::SpirvTools tools_;
+ std::unique_ptr<IRContext> context_;
+ spvtools::MessageConsumer consumer_;
+ uint32_t disassemble_options_;
+ std::string error_message_;
+};
+
+TEST_F(DecorationManagerTest,
+ ComparingDecorationsWithDiffOpcodesDecorateDecorateId) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // This parameter can be interprated both as { SpvDecorationConstant }
+ // and also as a list of IDs: { 22 }
+ const std::vector<uint32_t> param{SpvDecorationConstant};
+ // OpDecorate %1 Constant
+ Instruction inst1(
+ &ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_DECORATION, param}});
+ // OpDecorateId %1 %22 ; 'Constant' is decoration number 22
+ Instruction inst2(
+ &ir_context, SpvOpDecorateId, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_ID, param}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest,
+ ComparingDecorationsWithDiffOpcodesDecorateDecorateString) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // This parameter can be interprated both as { SpvDecorationConstant }
+ // and also as a null-terminated string with a single character with value 22.
+ const std::vector<uint32_t> param{SpvDecorationConstant};
+ // OpDecorate %1 Constant
+ Instruction inst1(
+ &ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_DECORATION, param}});
+ // OpDecorateStringGOOGLE %1 !22
+ Instruction inst2(
+ &ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_LITERAL_STRING, param}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest, ComparingDecorationsWithDiffDecorateParam) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpDecorate %1 Constant
+ Instruction inst1(&ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ // OpDecorate %1 Restrict
+ Instruction inst2(&ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationRestrict}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest, ComparingDecorationsWithDiffDecorateIdParam) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpDecorate %1 Constant
+ Instruction inst1(
+ &ir_context, SpvOpDecorateId, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_ID, {555}}});
+ // OpDecorate %1 Restrict
+ Instruction inst2(
+ &ir_context, SpvOpDecorateId, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_ID, {666}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest, ComparingDecorationsWithDiffDecorateStringParam) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpDecorate %1 Constant
+ Instruction inst1(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("Hello!")}});
+ // OpDecorate %1 Restrict
+ Instruction inst2(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("Hellx")}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest, ComparingSameDecorationsOnDiffTargetAllowed) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpDecorate %1 Constant
+ Instruction inst1(&ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ // OpDecorate %2 Constant
+ Instruction inst2(&ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {2u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest, ComparingSameDecorationIdsOnDiffTargetAllowed) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ Instruction inst1(
+ &ir_context, SpvOpDecorateId, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}}, {SPV_OPERAND_TYPE_DECORATION, {44}}});
+ Instruction inst2(
+ &ir_context, SpvOpDecorateId, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {2u}}, {SPV_OPERAND_TYPE_DECORATION, {44}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest,
+ ComparingSameDecorationStringsOnDiffTargetAllowed) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ Instruction inst1(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("hello")}});
+ Instruction inst2(&ir_context, SpvOpDecorateStringGOOGLE, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {2u}},
+ {SPV_OPERAND_TYPE_LITERAL_STRING, MakeVector("hello")}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest, ComparingSameDecorationsOnDiffTargetDisallowed) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpDecorate %1 Constant
+ Instruction inst1(&ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ // OpDecorate %2 Constant
+ Instruction inst2(&ir_context, SpvOpDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {2u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, false));
+}
+
+TEST_F(DecorationManagerTest, ComparingMemberDecorationsOnSameTypeDiffMember) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpMemberDecorate %1 0 Constant
+ Instruction inst1(&ir_context, SpvOpMemberDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ // OpMemberDecorate %1 1 Constant
+ Instruction inst2(&ir_context, SpvOpMemberDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest,
+ ComparingSameMemberDecorationsOnDiffTargetAllowed) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpMemberDecorate %1 0 Constant
+ Instruction inst1(&ir_context, SpvOpMemberDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ // OpMemberDecorate %2 0 Constant
+ Instruction inst2(&ir_context, SpvOpMemberDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {2u}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->AreDecorationsTheSame(&inst1, &inst2, true));
+}
+
+TEST_F(DecorationManagerTest,
+ ComparingSameMemberDecorationsOnDiffTargetDisallowed) {
+ IRContext ir_context(SPV_ENV_UNIVERSAL_1_2, GetConsumer());
+ // OpMemberDecorate %1 0 Constant
+ Instruction inst1(&ir_context, SpvOpMemberDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {1u}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ // OpMemberDecorate %2 0 Constant
+ Instruction inst2(&ir_context, SpvOpMemberDecorate, 0u, 0u,
+ {{SPV_OPERAND_TYPE_ID, {2u}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}},
+ {SPV_OPERAND_TYPE_DECORATION, {SpvDecorationConstant}}});
+ DecorationManager* decoManager = ir_context.get_decoration_mgr();
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->AreDecorationsTheSame(&inst1, &inst2, false));
+}
+
+TEST_F(DecorationManagerTest, RemoveDecorationFromVariable) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1 %3
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Uniform
+%3 = OpVariable %4 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ decoManager->RemoveDecorationsFrom(1u);
+ auto decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+ decorations = decoManager->GetDecorationsFor(3u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ const std::string expected_decorations = R"(OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %3
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Uniform
+%3 = OpVariable %4 Uniform
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, RemoveDecorationStringFromVariable) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello world"
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1 %3
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Uniform
+%3 = OpVariable %4 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ decoManager->RemoveDecorationsFrom(1u);
+ auto decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+ decorations = decoManager->GetDecorationsFor(3u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ const std::string expected_decorations = R"(OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %3
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Uniform
+%3 = OpVariable %4 Uniform
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, RemoveDecorationFromDecorationGroup) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1 %3
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Uniform
+%3 = OpVariable %4 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ decoManager->RemoveDecorationsFrom(2u);
+ auto decorations = decoManager->GetDecorationsFor(2u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+ decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ const std::string expected_decorations = R"(OpDecorate %1 Constant
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+ decorations = decoManager->GetDecorationsFor(3u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_THAT(ToText(decorations), "");
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+%2 = OpDecorationGroup
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Uniform
+%3 = OpVariable %4 Uniform
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest,
+ RemoveDecorationFromDecorationGroupKeepDeadDecorations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1
+%3 = OpTypeInt 32 0
+%1 = OpVariable %3 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ decoManager->RemoveDecorationsFrom(1u);
+ auto decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+ decorations = decoManager->GetDecorationsFor(2u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ const std::string expected_decorations = R"(OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+%3 = OpTypeInt 32 0
+%1 = OpVariable %3 Uniform
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, RemoveAllDecorationsAppliedByGroup) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1
+OpDecorate %3 BuiltIn VertexId
+%3 = OpDecorationGroup
+OpGroupDecorate %3 %1
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Input
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ decoManager->RemoveDecorationsFrom(1u, [](const Instruction& inst) {
+ return inst.opcode() == SpvOpDecorate &&
+ inst.GetSingleWordInOperand(0u) == 3u;
+ });
+ auto decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ std::string expected_decorations = R"(OpDecorate %1 Constant
+OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+ decorations = decoManager->GetDecorationsFor(2u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ expected_decorations = R"(OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1
+OpDecorate %3 BuiltIn VertexId
+%3 = OpDecorationGroup
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Input
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, RemoveSomeDecorationsAppliedByGroup) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1
+OpDecorate %3 BuiltIn VertexId
+OpDecorate %3 Invariant
+%3 = OpDecorationGroup
+OpGroupDecorate %3 %1
+%uint = OpTypeInt 32 0
+%1 = OpVariable %uint Input
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ decoManager->RemoveDecorationsFrom(1u, [](const Instruction& inst) {
+ return inst.opcode() == SpvOpDecorate &&
+ inst.GetSingleWordInOperand(0u) == 3u &&
+ inst.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn;
+ });
+ auto decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ std::string expected_decorations = R"(OpDecorate %1 Constant
+OpDecorate %1 Invariant
+OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+ decorations = decoManager->GetDecorationsFor(2u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ expected_decorations = R"(OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1
+OpDecorate %3 BuiltIn VertexId
+OpDecorate %3 Invariant
+%3 = OpDecorationGroup
+OpDecorate %1 Invariant
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Input
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, RemoveDecorationDecorate) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %1 Restrict
+%2 = OpTypeInt 32 0
+%1 = OpVariable %2 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ auto decorations = decoManager->GetDecorationsFor(1u, false);
+ decoManager->RemoveDecoration(decorations.front());
+ decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ const std::string expected_decorations = R"(OpDecorate %1 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+}
+
+TEST_F(DecorationManagerTest, RemoveDecorationStringDecorate) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "foobar"
+OpDecorate %1 Restrict
+%2 = OpTypeInt 32 0
+%1 = OpVariable %2 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ auto decorations = decoManager->GetDecorationsFor(1u, false);
+ decoManager->RemoveDecoration(decorations.front());
+ decorations = decoManager->GetDecorationsFor(1u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ const std::string expected_decorations = R"(OpDecorate %1 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+}
+
+TEST_F(DecorationManagerTest, CloneDecorations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1
+OpDecorate %3 BuiltIn VertexId
+OpDecorate %3 Invariant
+%3 = OpDecorationGroup
+OpGroupDecorate %3 %1
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Input
+%5 = OpVariable %4 Input
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ // Check cloning OpDecorate including group decorations.
+ auto decorations = decoManager->GetDecorationsFor(5u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+
+ decoManager->CloneDecorations(1u, 5u);
+ decorations = decoManager->GetDecorationsFor(5u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ std::string expected_decorations = R"(OpDecorate %5 Constant
+OpDecorate %2 Restrict
+OpDecorate %3 BuiltIn VertexId
+OpDecorate %3 Invariant
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ // Check that bookkeeping for ID 2 remains the same.
+ decorations = decoManager->GetDecorationsFor(2u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ expected_decorations = R"(OpDecorate %2 Restrict
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %2 %1 %5
+OpDecorate %3 BuiltIn VertexId
+OpDecorate %3 Invariant
+%3 = OpDecorationGroup
+OpGroupDecorate %3 %1 %5
+OpDecorate %5 Constant
+%4 = OpTypeInt 32 0
+%1 = OpVariable %4 Input
+%5 = OpVariable %4 Input
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, CloneDecorationsStringAndId) {
+ const std::string spirv = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "blah"
+OpDecorateId %1 HlslCounterBufferGOOGLE %2
+OpDecorate %1 Aliased
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Uniform %3
+%1 = OpVariable %4 Uniform
+%2 = OpVariable %4 Uniform
+%5 = OpVariable %4 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ // Check cloning OpDecorate including group decorations.
+ auto decorations = decoManager->GetDecorationsFor(5u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+
+ decoManager->CloneDecorations(1u, 5u);
+ decorations = decoManager->GetDecorationsFor(5u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ std::string expected_decorations =
+ R"(OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
+OpDecorateId %5 HlslCounterBufferGOOGLE %2
+OpDecorate %5 Aliased
+)";
+ EXPECT_THAT(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "blah"
+OpDecorateId %1 HlslCounterBufferGOOGLE %2
+OpDecorate %1 Aliased
+OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
+OpDecorateId %5 HlslCounterBufferGOOGLE %2
+OpDecorate %5 Aliased
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Uniform %3
+%1 = OpVariable %4 Uniform
+%2 = OpVariable %4 Uniform
+%5 = OpVariable %4 Uniform
+)";
+ EXPECT_THAT(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, CloneSomeDecorations) {
+ const std::string spirv = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorate %1 RelaxedPrecision
+OpDecorate %1 Restrict
+%2 = OpTypeInt 32 0
+%3 = OpTypePointer Function %2
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpFunction %4 None %5
+%7 = OpLabel
+%1 = OpVariable %3 Function
+%8 = OpUndef %2
+OpReturn
+OpFunctionEnd
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_EQ(GetErrorMessage(), "");
+
+ // Check cloning OpDecorate including group decorations.
+ auto decorations = decoManager->GetDecorationsFor(8u, false);
+ EXPECT_EQ(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+
+ decoManager->CloneDecorations(1u, 8u, {SpvDecorationRelaxedPrecision});
+ decorations = decoManager->GetDecorationsFor(8u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ std::string expected_decorations =
+ R"(OpDecorate %8 RelaxedPrecision
+)";
+ EXPECT_EQ(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorate %1 RelaxedPrecision
+OpDecorate %1 Restrict
+OpDecorate %8 RelaxedPrecision
+%2 = OpTypeInt 32 0
+%3 = OpTypePointer Function %2
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpFunction %4 None %5
+%7 = OpLabel
+%1 = OpVariable %3 Function
+%8 = OpUndef %2
+OpReturn
+OpFunctionEnd
+)";
+ EXPECT_EQ(ModuleToText(), expected_binary);
+}
+
+// Test cloning decoration for an id that is decorated via a group decoration.
+TEST_F(DecorationManagerTest, CloneSomeGroupDecorations) {
+ const std::string spirv = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 RelaxedPrecision
+OpDecorate %1 Restrict
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %2
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Function %3
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpFunction %5 None %6
+%8 = OpLabel
+%2 = OpVariable %4 Function
+%9 = OpUndef %3
+OpReturn
+OpFunctionEnd
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_EQ(GetErrorMessage(), "");
+
+ // Check cloning OpDecorate including group decorations.
+ auto decorations = decoManager->GetDecorationsFor(9u, false);
+ EXPECT_EQ(GetErrorMessage(), "");
+ EXPECT_TRUE(decorations.empty());
+
+ decoManager->CloneDecorations(2u, 9u, {SpvDecorationRelaxedPrecision});
+ decorations = decoManager->GetDecorationsFor(9u, false);
+ EXPECT_THAT(GetErrorMessage(), "");
+
+ std::string expected_decorations =
+ R"(OpDecorate %9 RelaxedPrecision
+)";
+ EXPECT_EQ(ToText(decorations), expected_decorations);
+
+ const std::string expected_binary = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 RelaxedPrecision
+OpDecorate %1 Restrict
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %2
+OpDecorate %9 RelaxedPrecision
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Function %3
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpFunction %5 None %6
+%8 = OpLabel
+%2 = OpVariable %4 Function
+%9 = OpUndef %3
+OpReturn
+OpFunctionEnd
+)";
+ EXPECT_EQ(ModuleToText(), expected_binary);
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithoutGroupsTrue) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+OpDecorate %1 Constant
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithoutGroupsFalse) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsIdWithoutGroupsTrue) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %nine
+OpDecorateId %3 MaxByteOffsetId %nine
+OpDecorateId %3 AlignmentId %nine
+OpDecorateId %1 MaxByteOffsetId %nine
+%u32 = OpTypeInt 32 0
+%nine = OpConstant %u32 9
+%1 = OpVariable %u32 Uniform
+%3 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 3u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsIdWithoutGroupsFalse) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %nine
+OpDecorateId %2 MaxByteOffsetId %nine
+OpDecorateId %2 AlignmentId %nine
+%u32 = OpTypeInt 32 0
+%nine = OpConstant %u32 9
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsStringWithoutGroupsTrue) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "world"
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsStringWithoutGroupsFalse) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello"
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithGroupsTrue) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %1 Constant
+OpDecorate %3 Restrict
+%3 = OpDecorationGroup
+OpGroupDecorate %3 %2
+OpDecorate %4 Invariant
+%4 = OpDecorationGroup
+OpGroupDecorate %4 %1 %2
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsWithGroupsFalse) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %1 Constant
+OpDecorate %4 Invariant
+%4 = OpDecorationGroup
+OpGroupDecorate %4 %1 %2
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsDuplicateDecorations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %2 Constant
+OpDecorate %2 Constant
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentVariations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Location 0
+OpDecorate %2 Location 1
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest,
+ HaveTheSameDecorationsDuplicateMemberDecorations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %1 0 Location 0
+OpMemberDecorate %2 0 Location 0
+OpMemberDecorate %2 0 Location 0
+%u32 = OpTypeInt 32 0
+%1 = OpTypeStruct %u32 %u32
+%2 = OpTypeStruct %u32 %u32
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest,
+ HaveTheSameDecorationsDifferentMemberSameDecoration) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %1 0 Location 0
+OpMemberDecorate %2 1 Location 0
+%u32 = OpTypeInt 32 0
+%1 = OpTypeStruct %u32 %u32
+%2 = OpTypeStruct %u32 %u32
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentMemberVariations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %1 0 Location 0
+OpMemberDecorate %2 0 Location 1
+%u32 = OpTypeInt 32 0
+%1 = OpTypeStruct %u32 %u32
+%2 = OpTypeStruct %u32 %u32
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsDuplicateIdDecorations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %2
+OpDecorateId %3 AlignmentId %2
+OpDecorateId %3 AlignmentId %2
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%3 = OpVariable %u32 Uniform
+%2 = OpSpecConstant %u32 0
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 3u));
+}
+
+TEST_F(DecorationManagerTest,
+ HaveTheSameDecorationsDuplicateStringDecorations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello"
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_TRUE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentIdVariations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %2
+OpDecorateId %3 AlignmentId %4
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%3 = OpVariable %u32 Uniform
+%2 = OpSpecConstant %u32 0
+%4 = OpSpecConstant %u32 0
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsDifferentStringVariations) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world"
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsLeftSymmetry) {
+ // Left being a subset of right is not enough.
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %1 Constant
+OpDecorate %2 Constant
+OpDecorate %2 Restrict
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationsRightSymmetry) {
+ // Right being a subset of left is not enough.
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %1 Restrict
+OpDecorate %2 Constant
+OpDecorate %2 Constant
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationIdsLeftSymmetry) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %nine
+OpDecorateId %1 AlignmentId %nine
+OpDecorateId %2 AlignmentId %nine
+OpDecorateId %2 MaxByteOffsetId %nine
+%u32 = OpTypeInt 32 0
+%nine = OpConstant %u32 9
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationIdsRightSymmetry) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorateId %1 AlignmentId %nine
+OpDecorateId %1 MaxByteOffsetId %nine
+OpDecorateId %2 AlignmentId %nine
+OpDecorateId %2 AlignmentId %nine
+%u32 = OpTypeInt 32 0
+%nine = OpConstant %u32 9
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationStringsLeftSymmetry) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "world"
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+TEST_F(DecorationManagerTest, HaveTheSameDecorationStringsRightSymmetry) {
+ const std::string spirv = R"(
+OpCapability Kernel
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpExtension "SPV_GOOGLE_decorate_string"
+OpMemoryModel Logical GLSL450
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "world"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello"
+OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello"
+%u32 = OpTypeInt 32 0
+%1 = OpVariable %u32 Uniform
+%2 = OpVariable %u32 Uniform
+)";
+ DecorationManager* decoManager = GetDecorationManager(spirv);
+ EXPECT_THAT(GetErrorMessage(), "");
+ EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u));
+}
+
+} // namespace
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/def_use_test.cpp b/test/opt/def_use_test.cpp
new file mode 100644
index 0000000..3b856ce
--- /dev/null
+++ b/test/opt/def_use_test.cpp
@@ -0,0 +1,1719 @@
+// Copyright (c) 2016 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 <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/def_use_manager.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "spirv-tools/libspirv.hpp"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+namespace {
+
+using ::testing::Contains;
+using ::testing::UnorderedElementsAre;
+using ::testing::UnorderedElementsAreArray;
+
+// Returns the number of uses of |id|.
+uint32_t NumUses(const std::unique_ptr<IRContext>& context, uint32_t id) {
+ uint32_t count = 0;
+ context->get_def_use_mgr()->ForEachUse(
+ id, [&count](Instruction*, uint32_t) { ++count; });
+ return count;
+}
+
+// Returns the opcode of each use of |id|.
+//
+// If |id| is used multiple times in a single instruction, that instruction's
+// opcode will appear a corresponding number of times.
+std::vector<SpvOp> GetUseOpcodes(const std::unique_ptr<IRContext>& context,
+ uint32_t id) {
+ std::vector<SpvOp> opcodes;
+ context->get_def_use_mgr()->ForEachUse(
+ id, [&opcodes](Instruction* user, uint32_t) {
+ opcodes.push_back(user->opcode());
+ });
+ return opcodes;
+}
+
+// Disassembles the given |inst| and returns the disassembly.
+std::string DisassembleInst(Instruction* inst) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
+
+ std::vector<uint32_t> binary;
+ // We need this to generate the necessary header in the binary.
+ tools.Assemble("", &binary);
+ inst->ToBinaryWithoutAttachedDebugInsts(&binary);
+
+ std::string text;
+ // We'll need to check the underlying id numbers.
+ // So turn off friendly names for ids.
+ tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ while (!text.empty() && text.back() == '\n') text.pop_back();
+ return text;
+}
+
+// A struct for holding expected id defs and uses.
+struct InstDefUse {
+ using IdInstPair = std::pair<uint32_t, std::string>;
+ using IdInstsPair = std::pair<uint32_t, std::vector<std::string>>;
+
+ // Ids and their corresponding def instructions.
+ std::vector<IdInstPair> defs;
+ // Ids and their corresponding use instructions.
+ std::vector<IdInstsPair> uses;
+};
+
+// Checks that the |actual_defs| and |actual_uses| are in accord with
+// |expected_defs_uses|.
+void CheckDef(const InstDefUse& expected_defs_uses,
+ const DefUseManager::IdToDefMap& actual_defs) {
+ // Check defs.
+ ASSERT_EQ(expected_defs_uses.defs.size(), actual_defs.size());
+ for (uint32_t i = 0; i < expected_defs_uses.defs.size(); ++i) {
+ const auto id = expected_defs_uses.defs[i].first;
+ const auto expected_def = expected_defs_uses.defs[i].second;
+ ASSERT_EQ(1u, actual_defs.count(id)) << "expected to def id [" << id << "]";
+ auto def = actual_defs.at(id);
+ if (def->opcode() != SpvOpConstant) {
+ // Constants don't disassemble properly without a full context.
+ EXPECT_EQ(expected_def, DisassembleInst(actual_defs.at(id)));
+ }
+ }
+}
+
+using UserMap = std::unordered_map<uint32_t, std::vector<Instruction*>>;
+
+// Creates a mapping of all definitions to their users (except OpConstant).
+//
+// OpConstants are skipped because they cannot be disassembled in isolation.
+UserMap BuildAllUsers(const DefUseManager* mgr, uint32_t idBound) {
+ UserMap userMap;
+ for (uint32_t id = 0; id != idBound; ++id) {
+ if (mgr->GetDef(id)) {
+ mgr->ForEachUser(id, [id, &userMap](Instruction* user) {
+ if (user->opcode() != SpvOpConstant) {
+ userMap[id].push_back(user);
+ }
+ });
+ }
+ }
+ return userMap;
+}
+
+// Constants don't disassemble properly without a full context, so skip them as
+// checks.
+void CheckUse(const InstDefUse& expected_defs_uses, const DefUseManager* mgr,
+ uint32_t idBound) {
+ UserMap actual_uses = BuildAllUsers(mgr, idBound);
+ // Check uses.
+ ASSERT_EQ(expected_defs_uses.uses.size(), actual_uses.size());
+ for (uint32_t i = 0; i < expected_defs_uses.uses.size(); ++i) {
+ const auto id = expected_defs_uses.uses[i].first;
+ const auto& expected_uses = expected_defs_uses.uses[i].second;
+
+ ASSERT_EQ(1u, actual_uses.count(id)) << "expected to use id [" << id << "]";
+ const auto& uses = actual_uses.at(id);
+
+ ASSERT_EQ(expected_uses.size(), uses.size())
+ << "id [" << id << "] # uses: expected: " << expected_uses.size()
+ << " actual: " << uses.size();
+
+ std::vector<std::string> actual_uses_disassembled;
+ for (const auto actual_use : uses) {
+ actual_uses_disassembled.emplace_back(DisassembleInst(actual_use));
+ }
+ EXPECT_THAT(actual_uses_disassembled,
+ UnorderedElementsAreArray(expected_uses));
+ }
+}
+
+// The following test case mimics how LLVM handles induction variables.
+// But, yeah, it's not very readable. However, we only care about the id
+// defs and uses. So, no need to make sure this is valid OpPhi construct.
+const char kOpPhiTestFunction[] =
+ " %1 = OpTypeVoid "
+ " %6 = OpTypeInt 32 0 "
+ "%10 = OpTypeFloat 32 "
+ "%16 = OpTypeBool "
+ " %3 = OpTypeFunction %1 "
+ " %8 = OpConstant %6 0 "
+ "%18 = OpConstant %6 1 "
+ "%12 = OpConstant %10 1.0 "
+ " %2 = OpFunction %1 None %3 "
+ " %4 = OpLabel "
+ " OpBranch %5 "
+
+ " %5 = OpLabel "
+ " %7 = OpPhi %6 %8 %4 %9 %5 "
+ "%11 = OpPhi %10 %12 %4 %13 %5 "
+ " %9 = OpIAdd %6 %7 %8 "
+ "%13 = OpFAdd %10 %11 %12 "
+ "%17 = OpSLessThan %16 %7 %18 "
+ " OpLoopMerge %19 %5 None "
+ " OpBranchConditional %17 %5 %19 "
+
+ "%19 = OpLabel "
+ " OpReturn "
+ " OpFunctionEnd";
+
+struct ParseDefUseCase {
+ const char* text;
+ InstDefUse du;
+};
+
+using ParseDefUseTest = ::testing::TestWithParam<ParseDefUseCase>;
+
+TEST_P(ParseDefUseTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ const std::vector<const char*> text = {tc.text};
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Analyze def and use.
+ DefUseManager manager(context->module());
+
+ CheckDef(tc.du, manager.id_to_defs());
+ CheckUse(tc.du, &manager, context->module()->IdBound());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, ParseDefUseTest,
+ ::testing::ValuesIn(std::vector<ParseDefUseCase>{
+ {"", {{}, {}}}, // no instruction
+ {"OpMemoryModel Logical GLSL450", {{}, {}}}, // no def and use
+ { // single def, no use
+ "%1 = OpString \"wow\"",
+ {
+ {{1, "%1 = OpString \"wow\""}}, // defs
+ {} // uses
+ }
+ },
+ { // multiple def, no use
+ "%1 = OpString \"hello\" "
+ "%2 = OpString \"world\" "
+ "%3 = OpTypeVoid",
+ {
+ { // defs
+ {1, "%1 = OpString \"hello\""},
+ {2, "%2 = OpString \"world\""},
+ {3, "%3 = OpTypeVoid"},
+ },
+ {} // uses
+ }
+ },
+ { // multiple def, multiple use
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3 "
+ "%3 = OpTypeMatrix %2 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 3"},
+ {3, "%3 = OpTypeMatrix %2 3"},
+ },
+ { // uses
+ {1, {"%2 = OpTypeVector %1 3"}},
+ {2, {"%3 = OpTypeMatrix %2 3"}},
+ }
+ }
+ },
+ { // multiple use of the same id
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 "
+ "%4 = OpTypeVector %1 4",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 2"},
+ {3, "%3 = OpTypeVector %1 3"},
+ {4, "%4 = OpTypeVector %1 4"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpTypeVector %1 2",
+ "%3 = OpTypeVector %1 3",
+ "%4 = OpTypeVector %1 4",
+ }
+ },
+ }
+ }
+ },
+ { // labels
+ "%1 = OpTypeVoid "
+ "%2 = OpTypeBool "
+ "%3 = OpTypeFunction %1 "
+ "%4 = OpConstantTrue %2 "
+ "%5 = OpFunction %1 None %3 "
+
+ "%6 = OpLabel "
+ "OpBranchConditional %4 %7 %8 "
+
+ "%7 = OpLabel "
+ "OpBranch %7 "
+
+ "%8 = OpLabel "
+ "OpReturn "
+
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeVoid"},
+ {2, "%2 = OpTypeBool"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpConstantTrue %2"},
+ {5, "%5 = OpFunction %1 None %3"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpLabel"},
+ },
+ { // uses
+ {1, {
+ "%3 = OpTypeFunction %1",
+ "%5 = OpFunction %1 None %3",
+ }
+ },
+ {2, {"%4 = OpConstantTrue %2"}},
+ {3, {"%5 = OpFunction %1 None %3"}},
+ {4, {"OpBranchConditional %4 %7 %8"}},
+ {7,
+ {
+ "OpBranchConditional %4 %7 %8",
+ "OpBranch %7",
+ }
+ },
+ {8, {"OpBranchConditional %4 %7 %8"}},
+ }
+ }
+ },
+ { // cross function
+ "%1 = OpTypeBool "
+ "%3 = OpTypeFunction %1 "
+ "%2 = OpFunction %1 None %3 "
+
+ "%4 = OpLabel "
+ "%5 = OpVariable %1 Function "
+ "%6 = OpFunctionCall %1 %2 %5 "
+ "OpReturnValue %6 "
+
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpVariable %1 Function"},
+ {6, "%6 = OpFunctionCall %1 %2 %5"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpFunction %1 None %3",
+ "%3 = OpTypeFunction %1",
+ "%5 = OpVariable %1 Function",
+ "%6 = OpFunctionCall %1 %2 %5",
+ }
+ },
+ {2, {"%6 = OpFunctionCall %1 %2 %5"}},
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {5, {"%6 = OpFunctionCall %1 %2 %5"}},
+ {6, {"OpReturnValue %6"}},
+ }
+ }
+ },
+ { // selection merge and loop merge
+ "%1 = OpTypeVoid "
+ "%3 = OpTypeFunction %1 "
+ "%10 = OpTypeBool "
+ "%8 = OpConstantTrue %10 "
+ "%2 = OpFunction %1 None %3 "
+
+ "%4 = OpLabel "
+ "OpLoopMerge %5 %4 None "
+ "OpBranch %6 "
+
+ "%5 = OpLabel "
+ "OpReturn "
+
+ "%6 = OpLabel "
+ "OpSelectionMerge %7 None "
+ "OpBranchConditional %8 %9 %7 "
+
+ "%7 = OpLabel "
+ "OpReturn "
+
+ "%9 = OpLabel "
+ "OpReturn "
+
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeVoid"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpConstantTrue %10"},
+ {9, "%9 = OpLabel"},
+ {10, "%10 = OpTypeBool"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpFunction %1 None %3",
+ "%3 = OpTypeFunction %1",
+ }
+ },
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4, {"OpLoopMerge %5 %4 None"}},
+ {5, {"OpLoopMerge %5 %4 None"}},
+ {6, {"OpBranch %6"}},
+ {7,
+ {
+ "OpSelectionMerge %7 None",
+ "OpBranchConditional %8 %9 %7",
+ }
+ },
+ {8, {"OpBranchConditional %8 %9 %7"}},
+ {9, {"OpBranchConditional %8 %9 %7"}},
+ {10, {"%8 = OpConstantTrue %10"}},
+ }
+ }
+ },
+ { // Forward reference
+ "OpDecorate %1 Block "
+ "OpTypeForwardPointer %2 Input "
+ "%3 = OpTypeInt 32 0 "
+ "%1 = OpTypeStruct %3 "
+ "%2 = OpTypePointer Input %3",
+ {
+ { // defs
+ {1, "%1 = OpTypeStruct %3"},
+ {2, "%2 = OpTypePointer Input %3"},
+ {3, "%3 = OpTypeInt 32 0"},
+ },
+ { // uses
+ {1, {"OpDecorate %1 Block"}},
+ {2, {"OpTypeForwardPointer %2 Input"}},
+ {3,
+ {
+ "%1 = OpTypeStruct %3",
+ "%2 = OpTypePointer Input %3",
+ }
+ }
+ },
+ },
+ },
+ { // OpPhi
+ kOpPhiTestFunction,
+ {
+ { // defs
+ {1, "%1 = OpTypeVoid"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpTypeInt 32 0"},
+ {7, "%7 = OpPhi %6 %8 %4 %9 %5"},
+ {8, "%8 = OpConstant %6 0"},
+ {9, "%9 = OpIAdd %6 %7 %8"},
+ {10, "%10 = OpTypeFloat 32"},
+ {11, "%11 = OpPhi %10 %12 %4 %13 %5"},
+ {12, "%12 = OpConstant %10 1.0"},
+ {13, "%13 = OpFAdd %10 %11 %12"},
+ {16, "%16 = OpTypeBool"},
+ {17, "%17 = OpSLessThan %16 %7 %18"},
+ {18, "%18 = OpConstant %6 1"},
+ {19, "%19 = OpLabel"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpFunction %1 None %3",
+ "%3 = OpTypeFunction %1",
+ }
+ },
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ }
+ },
+ {5,
+ {
+ "OpBranch %5",
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ {6,
+ {
+ // Can't check constants properly
+ // "%8 = OpConstant %6 0",
+ // "%18 = OpConstant %6 1",
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ "%9 = OpIAdd %6 %7 %8",
+ }
+ },
+ {7,
+ {
+ "%9 = OpIAdd %6 %7 %8",
+ "%17 = OpSLessThan %16 %7 %18",
+ }
+ },
+ {8,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ "%9 = OpIAdd %6 %7 %8",
+ }
+ },
+ {9, {"%7 = OpPhi %6 %8 %4 %9 %5"}},
+ {10,
+ {
+ // "%12 = OpConstant %10 1.0",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "%13 = OpFAdd %10 %11 %12",
+ }
+ },
+ {11, {"%13 = OpFAdd %10 %11 %12"}},
+ {12,
+ {
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "%13 = OpFAdd %10 %11 %12",
+ }
+ },
+ {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ {16, {"%17 = OpSLessThan %16 %7 %18"}},
+ {17, {"OpBranchConditional %17 %5 %19"}},
+ {18, {"%17 = OpSLessThan %16 %7 %18"}},
+ {19,
+ {
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ },
+ },
+ },
+ { // OpPhi defining and referencing the same id.
+ "%1 = OpTypeBool "
+ "%3 = OpTypeFunction %1 "
+ "%2 = OpConstantTrue %1 "
+ "%4 = OpFunction %1 None %3 "
+ "%6 = OpLabel "
+ " OpBranch %7 "
+ "%7 = OpLabel "
+ "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8
+ " OpBranch %7 "
+ " OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpFunction %1 None %3"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpPhi %1 %8 %7 %2 %6"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpConstantTrue %1",
+ "%3 = OpTypeFunction %1",
+ "%4 = OpFunction %1 None %3",
+ "%8 = OpPhi %1 %8 %7 %2 %6",
+ }
+ },
+ {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {3, {"%4 = OpFunction %1 None %3"}},
+ {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {7,
+ {
+ "OpBranch %7",
+ "%8 = OpPhi %1 %8 %7 %2 %6",
+ "OpBranch %7",
+ }
+ },
+ {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ },
+ },
+ },
+ })
+);
+// clang-format on
+
+struct ReplaceUseCase {
+ const char* before;
+ std::vector<std::pair<uint32_t, uint32_t>> candidates;
+ const char* after;
+ InstDefUse du;
+};
+
+using ReplaceUseTest = ::testing::TestWithParam<ReplaceUseCase>;
+
+// Disassembles the given |module| and returns the disassembly.
+std::string DisassembleModule(Module* module) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
+
+ std::vector<uint32_t> binary;
+ module->ToBinary(&binary, /* skip_nop = */ false);
+
+ std::string text;
+ // We'll need to check the underlying id numbers.
+ // So turn off friendly names for ids.
+ tools.Disassemble(binary, &text, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ while (!text.empty() && text.back() == '\n') text.pop_back();
+ return text;
+}
+
+TEST_P(ReplaceUseTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ const std::vector<const char*> text = {tc.before};
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Force a re-build of def-use manager.
+ context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
+ (void)context->get_def_use_mgr();
+
+ // Do the substitution.
+ for (const auto& candidate : tc.candidates) {
+ context->ReplaceAllUsesWith(candidate.first, candidate.second);
+ }
+
+ EXPECT_EQ(tc.after, DisassembleModule(context->module()));
+ CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs());
+ CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, ReplaceUseTest,
+ ::testing::ValuesIn(std::vector<ReplaceUseCase>{
+ { // no use, no replace request
+ "", {}, "", {},
+ },
+ { // replace one use
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3 "
+ "%3 = OpTypeInt 32 0 ",
+ {{1, 3}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %3 3\n"
+ "%3 = OpTypeInt 32 0",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %3 3"},
+ {3, "%3 = OpTypeInt 32 0"},
+ },
+ { // uses
+ {3, {"%2 = OpTypeVector %3 3"}},
+ },
+ },
+ },
+ { // replace and then replace back
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3 "
+ "%3 = OpTypeInt 32 0",
+ {{1, 3}, {3, 1}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %1 3\n"
+ "%3 = OpTypeInt 32 0",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 3"},
+ {3, "%3 = OpTypeInt 32 0"},
+ },
+ { // uses
+ {1, {"%2 = OpTypeVector %1 3"}},
+ },
+ },
+ },
+ { // replace with the same id
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3",
+ {{1, 1}, {2, 2}, {3, 3}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %1 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 3"},
+ },
+ { // uses
+ {1, {"%2 = OpTypeVector %1 3"}},
+ },
+ },
+ },
+ { // replace in sequence
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 3 "
+ "%3 = OpTypeInt 32 0 "
+ "%4 = OpTypeInt 32 1 ",
+ {{1, 3}, {3, 4}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %4 3\n"
+ "%3 = OpTypeInt 32 0\n"
+ "%4 = OpTypeInt 32 1",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %4 3"},
+ {3, "%3 = OpTypeInt 32 0"},
+ {4, "%4 = OpTypeInt 32 1"},
+ },
+ { // uses
+ {4, {"%2 = OpTypeVector %4 3"}},
+ },
+ },
+ },
+ { // replace multiple uses
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 "
+ "%4 = OpTypeVector %1 4 "
+ "%5 = OpTypeMatrix %2 2 "
+ "%6 = OpTypeMatrix %3 3 "
+ "%7 = OpTypeMatrix %4 4 "
+ "%8 = OpTypeInt 32 0 "
+ "%9 = OpTypeInt 32 1 "
+ "%10 = OpTypeInt 64 0",
+ {{1, 8}, {2, 9}, {4, 10}},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %8 2\n"
+ "%3 = OpTypeVector %8 3\n"
+ "%4 = OpTypeVector %8 4\n"
+ "%5 = OpTypeMatrix %9 2\n"
+ "%6 = OpTypeMatrix %3 3\n"
+ "%7 = OpTypeMatrix %10 4\n"
+ "%8 = OpTypeInt 32 0\n"
+ "%9 = OpTypeInt 32 1\n"
+ "%10 = OpTypeInt 64 0",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %8 2"},
+ {3, "%3 = OpTypeVector %8 3"},
+ {4, "%4 = OpTypeVector %8 4"},
+ {5, "%5 = OpTypeMatrix %9 2"},
+ {6, "%6 = OpTypeMatrix %3 3"},
+ {7, "%7 = OpTypeMatrix %10 4"},
+ {8, "%8 = OpTypeInt 32 0"},
+ {9, "%9 = OpTypeInt 32 1"},
+ {10, "%10 = OpTypeInt 64 0"},
+ },
+ { // uses
+ {8,
+ {
+ "%2 = OpTypeVector %8 2",
+ "%3 = OpTypeVector %8 3",
+ "%4 = OpTypeVector %8 4",
+ }
+ },
+ {9, {"%5 = OpTypeMatrix %9 2"}},
+ {3, {"%6 = OpTypeMatrix %3 3"}},
+ {10, {"%7 = OpTypeMatrix %10 4"}},
+ },
+ },
+ },
+ { // OpPhi.
+ kOpPhiTestFunction,
+ // replace one id used by OpPhi, replace one id generated by OpPhi
+ {{9, 13}, {11, 9}},
+ "%1 = OpTypeVoid\n"
+ "%6 = OpTypeInt 32 0\n"
+ "%10 = OpTypeFloat 32\n"
+ "%16 = OpTypeBool\n"
+ "%3 = OpTypeFunction %1\n"
+ "%8 = OpConstant %6 0\n"
+ "%18 = OpConstant %6 1\n"
+ "%12 = OpConstant %10 1\n"
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "OpBranch %5\n"
+
+ "%5 = OpLabel\n"
+ "%7 = OpPhi %6 %8 %4 %13 %5\n" // %9 -> %13
+ "%11 = OpPhi %10 %12 %4 %13 %5\n"
+ "%9 = OpIAdd %6 %7 %8\n"
+ "%13 = OpFAdd %10 %9 %12\n" // %11 -> %9
+ "%17 = OpSLessThan %16 %7 %18\n"
+ "OpLoopMerge %19 %5 None\n"
+ "OpBranchConditional %17 %5 %19\n"
+
+ "%19 = OpLabel\n"
+ "OpReturn\n"
+ "OpFunctionEnd",
+ {
+ { // defs.
+ {1, "%1 = OpTypeVoid"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpTypeInt 32 0"},
+ {7, "%7 = OpPhi %6 %8 %4 %13 %5"},
+ {8, "%8 = OpConstant %6 0"},
+ {9, "%9 = OpIAdd %6 %7 %8"},
+ {10, "%10 = OpTypeFloat 32"},
+ {11, "%11 = OpPhi %10 %12 %4 %13 %5"},
+ {12, "%12 = OpConstant %10 1.0"},
+ {13, "%13 = OpFAdd %10 %9 %12"},
+ {16, "%16 = OpTypeBool"},
+ {17, "%17 = OpSLessThan %16 %7 %18"},
+ {18, "%18 = OpConstant %6 1"},
+ {19, "%19 = OpLabel"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpFunction %1 None %3",
+ "%3 = OpTypeFunction %1",
+ }
+ },
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4,
+ {
+ "%7 = OpPhi %6 %8 %4 %13 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ }
+ },
+ {5,
+ {
+ "OpBranch %5",
+ "%7 = OpPhi %6 %8 %4 %13 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ {6,
+ {
+ // Can't properly check constants
+ // "%8 = OpConstant %6 0",
+ // "%18 = OpConstant %6 1",
+ "%7 = OpPhi %6 %8 %4 %13 %5",
+ "%9 = OpIAdd %6 %7 %8"
+ }
+ },
+ {7,
+ {
+ "%9 = OpIAdd %6 %7 %8",
+ "%17 = OpSLessThan %16 %7 %18",
+ }
+ },
+ {8,
+ {
+ "%7 = OpPhi %6 %8 %4 %13 %5",
+ "%9 = OpIAdd %6 %7 %8",
+ }
+ },
+ {9, {"%13 = OpFAdd %10 %9 %12"}}, // uses of %9 changed from %7 to %13
+ {10,
+ {
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ // "%12 = OpConstant %10 1",
+ "%13 = OpFAdd %10 %9 %12"
+ }
+ },
+ // no more uses of %11
+ {12,
+ {
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ "%13 = OpFAdd %10 %9 %12"
+ }
+ },
+ {13, {
+ "%7 = OpPhi %6 %8 %4 %13 %5",
+ "%11 = OpPhi %10 %12 %4 %13 %5",
+ }
+ },
+ {16, {"%17 = OpSLessThan %16 %7 %18"}},
+ {17, {"OpBranchConditional %17 %5 %19"}},
+ {18, {"%17 = OpSLessThan %16 %7 %18"}},
+ {19,
+ {
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ },
+ },
+ },
+ { // OpPhi defining and referencing the same id.
+ "%1 = OpTypeBool "
+ "%3 = OpTypeFunction %1 "
+ "%2 = OpConstantTrue %1 "
+
+ "%4 = OpFunction %3 None %1 "
+ "%6 = OpLabel "
+ " OpBranch %7 "
+ "%7 = OpLabel "
+ "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8
+ " OpBranch %7 "
+ " OpFunctionEnd",
+ {{8, 2}},
+ "%1 = OpTypeBool\n"
+ "%3 = OpTypeFunction %1\n"
+ "%2 = OpConstantTrue %1\n"
+
+ "%4 = OpFunction %3 None %1\n"
+ "%6 = OpLabel\n"
+ "OpBranch %7\n"
+ "%7 = OpLabel\n"
+ "%8 = OpPhi %1 %2 %7 %2 %6\n" // use of %8 changed to %2
+ "OpBranch %7\n"
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpFunction %3 None %1"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpPhi %1 %2 %7 %2 %6"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpConstantTrue %1",
+ "%3 = OpTypeFunction %1",
+ "%4 = OpFunction %3 None %1",
+ "%8 = OpPhi %1 %2 %7 %2 %6",
+ }
+ },
+ {2,
+ {
+ // Only checking users
+ "%8 = OpPhi %1 %2 %7 %2 %6",
+ }
+ },
+ {3, {"%4 = OpFunction %3 None %1"}},
+ {6, {"%8 = OpPhi %1 %2 %7 %2 %6"}},
+ {7,
+ {
+ "OpBranch %7",
+ "%8 = OpPhi %1 %2 %7 %2 %6",
+ "OpBranch %7",
+ }
+ },
+ // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ },
+ },
+ },
+ })
+);
+// clang-format on
+
+struct KillDefCase {
+ const char* before;
+ std::vector<uint32_t> ids_to_kill;
+ const char* after;
+ InstDefUse du;
+};
+
+using KillDefTest = ::testing::TestWithParam<KillDefCase>;
+
+TEST_P(KillDefTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ const std::vector<const char*> text = {tc.before};
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Analyze def and use.
+ DefUseManager manager(context->module());
+
+ // Do the substitution.
+ for (const auto id : tc.ids_to_kill) context->KillDef(id);
+
+ EXPECT_EQ(tc.after, DisassembleModule(context->module()));
+ CheckDef(tc.du, context->get_def_use_mgr()->id_to_defs());
+ CheckUse(tc.du, context->get_def_use_mgr(), context->module()->IdBound());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, KillDefTest,
+ ::testing::ValuesIn(std::vector<KillDefCase>{
+ { // no def, no use, no kill
+ "", {}, "", {}
+ },
+ { // kill nothing
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 ",
+ {},
+ "%1 = OpTypeBool\n"
+ "%2 = OpTypeVector %1 2\n"
+ "%3 = OpTypeVector %1 3",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpTypeVector %1 2"},
+ {3, "%3 = OpTypeVector %1 3"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpTypeVector %1 2",
+ "%3 = OpTypeVector %1 3",
+ }
+ },
+ },
+ },
+ },
+ { // kill id used, kill id not used, kill id not defined
+ "%1 = OpTypeBool "
+ "%2 = OpTypeVector %1 2 "
+ "%3 = OpTypeVector %1 3 "
+ "%4 = OpTypeVector %1 4 "
+ "%5 = OpTypeMatrix %3 3 "
+ "%6 = OpTypeMatrix %2 3",
+ {1, 3, 5, 10}, // ids to kill
+ "%2 = OpTypeVector %1 2\n"
+ "%4 = OpTypeVector %1 4\n"
+ "%6 = OpTypeMatrix %2 3",
+ {
+ { // defs
+ {2, "%2 = OpTypeVector %1 2"},
+ {4, "%4 = OpTypeVector %1 4"},
+ {6, "%6 = OpTypeMatrix %2 3"},
+ },
+ { // uses. %1 and %3 are both killed, so no uses
+ // recorded for them anymore.
+ {2, {"%6 = OpTypeMatrix %2 3"}},
+ }
+ },
+ },
+ { // OpPhi.
+ kOpPhiTestFunction,
+ {9, 11}, // kill one id used by OpPhi, kill one id generated by OpPhi
+ "%1 = OpTypeVoid\n"
+ "%6 = OpTypeInt 32 0\n"
+ "%10 = OpTypeFloat 32\n"
+ "%16 = OpTypeBool\n"
+ "%3 = OpTypeFunction %1\n"
+ "%8 = OpConstant %6 0\n"
+ "%18 = OpConstant %6 1\n"
+ "%12 = OpConstant %10 1\n"
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "OpBranch %5\n"
+
+ "%5 = OpLabel\n"
+ "%7 = OpPhi %6 %8 %4 %9 %5\n"
+ "%13 = OpFAdd %10 %11 %12\n"
+ "%17 = OpSLessThan %16 %7 %18\n"
+ "OpLoopMerge %19 %5 None\n"
+ "OpBranchConditional %17 %5 %19\n"
+
+ "%19 = OpLabel\n"
+ "OpReturn\n"
+ "OpFunctionEnd",
+ {
+ { // defs. %9 & %11 are killed.
+ {1, "%1 = OpTypeVoid"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpLabel"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpTypeInt 32 0"},
+ {7, "%7 = OpPhi %6 %8 %4 %9 %5"},
+ {8, "%8 = OpConstant %6 0"},
+ {10, "%10 = OpTypeFloat 32"},
+ {12, "%12 = OpConstant %10 1.0"},
+ {13, "%13 = OpFAdd %10 %11 %12"},
+ {16, "%16 = OpTypeBool"},
+ {17, "%17 = OpSLessThan %16 %7 %18"},
+ {18, "%18 = OpConstant %6 1"},
+ {19, "%19 = OpLabel"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpFunction %1 None %3",
+ "%3 = OpTypeFunction %1",
+ }
+ },
+ {3, {"%2 = OpFunction %1 None %3"}},
+ {4,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ // "%11 = OpPhi %10 %12 %4 %13 %5",
+ }
+ },
+ {5,
+ {
+ "OpBranch %5",
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ // "%11 = OpPhi %10 %12 %4 %13 %5",
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ {6,
+ {
+ // Can't properly check constants
+ // "%8 = OpConstant %6 0",
+ // "%18 = OpConstant %6 1",
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ // "%9 = OpIAdd %6 %7 %8"
+ }
+ },
+ {7, {"%17 = OpSLessThan %16 %7 %18"}},
+ {8,
+ {
+ "%7 = OpPhi %6 %8 %4 %9 %5",
+ // "%9 = OpIAdd %6 %7 %8",
+ }
+ },
+ // {9, {"%7 = OpPhi %6 %8 %4 %13 %5"}},
+ {10,
+ {
+ // "%11 = OpPhi %10 %12 %4 %13 %5",
+ // "%12 = OpConstant %10 1",
+ "%13 = OpFAdd %10 %11 %12"
+ }
+ },
+ // {11, {"%13 = OpFAdd %10 %11 %12"}},
+ {12,
+ {
+ // "%11 = OpPhi %10 %12 %4 %13 %5",
+ "%13 = OpFAdd %10 %11 %12"
+ }
+ },
+ // {13, {"%11 = OpPhi %10 %12 %4 %13 %5"}},
+ {16, {"%17 = OpSLessThan %16 %7 %18"}},
+ {17, {"OpBranchConditional %17 %5 %19"}},
+ {18, {"%17 = OpSLessThan %16 %7 %18"}},
+ {19,
+ {
+ "OpLoopMerge %19 %5 None",
+ "OpBranchConditional %17 %5 %19",
+ }
+ },
+ },
+ },
+ },
+ { // OpPhi defining and referencing the same id.
+ "%1 = OpTypeBool "
+ "%3 = OpTypeFunction %1 "
+ "%2 = OpConstantTrue %1 "
+ "%4 = OpFunction %3 None %1 "
+ "%6 = OpLabel "
+ " OpBranch %7 "
+ "%7 = OpLabel "
+ "%8 = OpPhi %1 %8 %7 %2 %6 " // both defines and uses %8
+ " OpBranch %7 "
+ " OpFunctionEnd",
+ {8},
+ "%1 = OpTypeBool\n"
+ "%3 = OpTypeFunction %1\n"
+ "%2 = OpConstantTrue %1\n"
+
+ "%4 = OpFunction %3 None %1\n"
+ "%6 = OpLabel\n"
+ "OpBranch %7\n"
+ "%7 = OpLabel\n"
+ "OpBranch %7\n"
+ "OpFunctionEnd",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ {3, "%3 = OpTypeFunction %1"},
+ {4, "%4 = OpFunction %3 None %1"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ // {8, "%8 = OpPhi %1 %8 %7 %2 %6"},
+ },
+ { // uses
+ {1,
+ {
+ "%2 = OpConstantTrue %1",
+ "%3 = OpTypeFunction %1",
+ "%4 = OpFunction %3 None %1",
+ // "%8 = OpPhi %1 %8 %7 %2 %6",
+ }
+ },
+ // {2, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {3, {"%4 = OpFunction %3 None %1"}},
+ // {6, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ {7,
+ {
+ "OpBranch %7",
+ // "%8 = OpPhi %1 %8 %7 %2 %6",
+ "OpBranch %7",
+ }
+ },
+ // {8, {"%8 = OpPhi %1 %8 %7 %2 %6"}},
+ },
+ },
+ },
+ })
+);
+// clang-format on
+
+TEST(DefUseTest, OpSwitch) {
+ // Because disassembler has basic type check for OpSwitch's selector, we
+ // cannot use the DisassembleInst() in the above. Thus, this special spotcheck
+ // test case.
+
+ const char original_text[] =
+ // int64 f(int64 v) {
+ // switch (v) {
+ // case 1: break;
+ // case -4294967296: break;
+ // case 9223372036854775807: break;
+ // default: break;
+ // }
+ // return v;
+ // }
+ " %1 = OpTypeInt 64 1 "
+ " %3 = OpTypePointer Input %1 "
+ " %2 = OpFunction %1 None %3 " // %3 is int64(int64)*
+ " %4 = OpFunctionParameter %1 "
+ " %5 = OpLabel "
+ " %6 = OpLoad %1 %4 " // selector value
+ " OpSelectionMerge %7 None "
+ " OpSwitch %6 %8 "
+ " 1 %9 " // 1
+ " -4294967296 %10 " // -2^32
+ " 9223372036854775807 %11 " // 2^63-1
+ " %8 = OpLabel " // default
+ " OpBranch %7 "
+ " %9 = OpLabel "
+ " OpBranch %7 "
+ "%10 = OpLabel "
+ " OpBranch %7 "
+ "%11 = OpLabel "
+ " OpBranch %7 "
+ " %7 = OpLabel "
+ " OpReturnValue %6 "
+ " OpFunctionEnd";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, original_text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Force a re-build of def-use manager.
+ context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
+ (void)context->get_def_use_mgr();
+
+ // Do a bunch replacements.
+ context->ReplaceAllUsesWith(11, 7); // to existing id
+ context->ReplaceAllUsesWith(10, 11); // to existing id
+ context->ReplaceAllUsesWith(9, 10); // to existing id
+
+ // clang-format off
+ const char modified_text[] =
+ "%1 = OpTypeInt 64 1\n"
+ "%3 = OpTypePointer Input %1\n"
+ "%2 = OpFunction %1 None %3\n" // %3 is int64(int64)*
+ "%4 = OpFunctionParameter %1\n"
+ "%5 = OpLabel\n"
+ "%6 = OpLoad %1 %4\n" // selector value
+ "OpSelectionMerge %7 None\n"
+ "OpSwitch %6 %8 1 %10 -4294967296 %11 9223372036854775807 %7\n" // changed!
+ "%8 = OpLabel\n" // default
+ "OpBranch %7\n"
+ "%9 = OpLabel\n"
+ "OpBranch %7\n"
+ "%10 = OpLabel\n"
+ "OpBranch %7\n"
+ "%11 = OpLabel\n"
+ "OpBranch %7\n"
+ "%7 = OpLabel\n"
+ "OpReturnValue %6\n"
+ "OpFunctionEnd";
+ // clang-format on
+
+ EXPECT_EQ(modified_text, DisassembleModule(context->module()));
+
+ InstDefUse def_uses = {};
+ def_uses.defs = {
+ {1, "%1 = OpTypeInt 64 1"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {3, "%3 = OpTypePointer Input %1"},
+ {4, "%4 = OpFunctionParameter %1"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpLoad %1 %4"},
+ {7, "%7 = OpLabel"},
+ {8, "%8 = OpLabel"},
+ {9, "%9 = OpLabel"},
+ {10, "%10 = OpLabel"},
+ {11, "%11 = OpLabel"},
+ };
+ CheckDef(def_uses, context->get_def_use_mgr()->id_to_defs());
+
+ {
+ EXPECT_EQ(2u, NumUses(context, 6));
+ std::vector<SpvOp> opcodes = GetUseOpcodes(context, 6u);
+ EXPECT_THAT(opcodes, UnorderedElementsAre(SpvOpSwitch, SpvOpReturnValue));
+ }
+ {
+ EXPECT_EQ(6u, NumUses(context, 7));
+ std::vector<SpvOp> opcodes = GetUseOpcodes(context, 7u);
+ // OpSwitch is now a user of %7.
+ EXPECT_THAT(opcodes, UnorderedElementsAre(SpvOpSelectionMerge, SpvOpBranch,
+ SpvOpBranch, SpvOpBranch,
+ SpvOpBranch, SpvOpSwitch));
+ }
+ // Check all ids only used by OpSwitch after replacement.
+ for (const auto id : {8u, 10u, 11u}) {
+ EXPECT_EQ(1u, NumUses(context, id));
+ EXPECT_EQ(SpvOpSwitch, GetUseOpcodes(context, id).back());
+ }
+}
+
+// Test case for analyzing individual instructions.
+struct AnalyzeInstDefUseTestCase {
+ const char* module_text;
+ InstDefUse expected_define_use;
+};
+
+using AnalyzeInstDefUseTest =
+ ::testing::TestWithParam<AnalyzeInstDefUseTestCase>;
+
+// Test the analyzing result for individual instructions.
+TEST_P(AnalyzeInstDefUseTest, Case) {
+ auto tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.module_text);
+ ASSERT_NE(nullptr, context);
+
+ // Analyze the instructions.
+ DefUseManager manager(context->module());
+
+ CheckDef(tc.expected_define_use, manager.id_to_defs());
+ CheckUse(tc.expected_define_use, &manager, context->module()->IdBound());
+ // CheckUse(tc.expected_define_use, manager.id_to_uses());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, AnalyzeInstDefUseTest,
+ ::testing::ValuesIn(std::vector<AnalyzeInstDefUseTestCase>{
+ { // A type declaring instruction.
+ "%1 = OpTypeInt 32 1",
+ {
+ // defs
+ {{1, "%1 = OpTypeInt 32 1"}},
+ {}, // no uses
+ },
+ },
+ { // A type declaring instruction and a constant value.
+ "%1 = OpTypeBool "
+ "%2 = OpConstantTrue %1",
+ {
+ { // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ },
+ { // uses
+ {1, {"%2 = OpConstantTrue %1"}},
+ },
+ },
+ },
+ }));
+// clang-format on
+
+using AnalyzeInstDefUse = ::testing::Test;
+
+TEST(AnalyzeInstDefUse, UseWithNoResultId) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+
+ // Analyze the instructions.
+ DefUseManager manager(context.module());
+
+ Instruction label(&context, SpvOpLabel, 0, 2, {});
+ manager.AnalyzeInstDefUse(&label);
+
+ Instruction branch(&context, SpvOpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {2}}});
+ manager.AnalyzeInstDefUse(&branch);
+ context.module()->SetIdBound(3);
+
+ InstDefUse expected = {
+ // defs
+ {
+ {2, "%2 = OpLabel"},
+ },
+ // uses
+ {{2, {"OpBranch %2"}}},
+ };
+
+ CheckDef(expected, manager.id_to_defs());
+ CheckUse(expected, &manager, context.module()->IdBound());
+}
+
+TEST(AnalyzeInstDefUse, AddNewInstruction) {
+ const std::string input = "%1 = OpTypeBool";
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input);
+ ASSERT_NE(nullptr, context);
+
+ // Analyze the instructions.
+ DefUseManager manager(context->module());
+
+ Instruction newInst(context.get(), SpvOpConstantTrue, 1, 2, {});
+ manager.AnalyzeInstDefUse(&newInst);
+
+ InstDefUse expected = {
+ {
+ // defs
+ {1, "%1 = OpTypeBool"},
+ {2, "%2 = OpConstantTrue %1"},
+ },
+ {
+ // uses
+ {1, {"%2 = OpConstantTrue %1"}},
+ },
+ };
+
+ CheckDef(expected, manager.id_to_defs());
+ CheckUse(expected, &manager, context->module()->IdBound());
+}
+
+struct KillInstTestCase {
+ const char* before;
+ std::unordered_set<uint32_t> indices_for_inst_to_kill;
+ const char* after;
+ InstDefUse expected_define_use;
+};
+
+using KillInstTest = ::testing::TestWithParam<KillInstTestCase>;
+
+TEST_P(KillInstTest, Case) {
+ auto tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.before,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Force a re-build of the def-use manager.
+ context->InvalidateAnalyses(IRContext::Analysis::kAnalysisDefUse);
+ (void)context->get_def_use_mgr();
+
+ // KillInst
+ context->module()->ForEachInst([&tc, &context](Instruction* inst) {
+ if (tc.indices_for_inst_to_kill.count(inst->result_id())) {
+ context->KillInst(inst);
+ }
+ });
+
+ EXPECT_EQ(tc.after, DisassembleModule(context->module()));
+ CheckDef(tc.expected_define_use, context->get_def_use_mgr()->id_to_defs());
+ CheckUse(tc.expected_define_use, context->get_def_use_mgr(),
+ context->module()->IdBound());
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, KillInstTest,
+ ::testing::ValuesIn(std::vector<KillInstTestCase>{
+ // Kill id defining instructions.
+ {
+ "%3 = OpTypeVoid "
+ "%1 = OpTypeFunction %3 "
+ "%2 = OpFunction %1 None %3 "
+ "%4 = OpLabel "
+ " OpBranch %5 "
+ "%5 = OpLabel "
+ " OpBranch %6 "
+ "%6 = OpLabel "
+ " OpBranch %4 "
+ "%7 = OpLabel "
+ " OpReturn "
+ " OpFunctionEnd",
+ {3, 5, 7},
+ "%1 = OpTypeFunction %3\n"
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "OpBranch %5\n"
+ "OpNop\n"
+ "OpBranch %6\n"
+ "%6 = OpLabel\n"
+ "OpBranch %4\n"
+ "OpNop\n"
+ "OpReturn\n"
+ "OpFunctionEnd",
+ {
+ // defs
+ {
+ {1, "%1 = OpTypeFunction %3"},
+ {2, "%2 = OpFunction %1 None %3"},
+ {4, "%4 = OpLabel"},
+ {6, "%6 = OpLabel"},
+ },
+ // uses
+ {
+ {1, {"%2 = OpFunction %1 None %3"}},
+ {4, {"OpBranch %4"}},
+ {6, {"OpBranch %6"}},
+ }
+ }
+ },
+ // Kill instructions that do not have result ids.
+ {
+ "%3 = OpTypeVoid "
+ "%1 = OpTypeFunction %3 "
+ "%2 = OpFunction %1 None %3 "
+ "%4 = OpLabel "
+ " OpBranch %5 "
+ "%5 = OpLabel "
+ " OpBranch %6 "
+ "%6 = OpLabel "
+ " OpBranch %4 "
+ "%7 = OpLabel "
+ " OpReturn "
+ " OpFunctionEnd",
+ {2, 4},
+ "%3 = OpTypeVoid\n"
+ "%1 = OpTypeFunction %3\n"
+ "OpNop\n"
+ "OpNop\n"
+ "OpBranch %5\n"
+ "%5 = OpLabel\n"
+ "OpBranch %6\n"
+ "%6 = OpLabel\n"
+ "OpBranch %4\n"
+ "%7 = OpLabel\n"
+ "OpReturn\n"
+ "OpFunctionEnd",
+ {
+ // defs
+ {
+ {1, "%1 = OpTypeFunction %3"},
+ {3, "%3 = OpTypeVoid"},
+ {5, "%5 = OpLabel"},
+ {6, "%6 = OpLabel"},
+ {7, "%7 = OpLabel"},
+ },
+ // uses
+ {
+ {3, {"%1 = OpTypeFunction %3"}},
+ {5, {"OpBranch %5"}},
+ {6, {"OpBranch %6"}},
+ }
+ }
+ },
+ }));
+// clang-format on
+
+struct GetAnnotationsTestCase {
+ const char* code;
+ uint32_t id;
+ std::vector<std::string> annotations;
+};
+
+using GetAnnotationsTest = ::testing::TestWithParam<GetAnnotationsTestCase>;
+
+TEST_P(GetAnnotationsTest, Case) {
+ const GetAnnotationsTestCase& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.code);
+ ASSERT_NE(nullptr, context);
+
+ // Get annotations
+ DefUseManager manager(context->module());
+ auto insts = manager.GetAnnotations(tc.id);
+
+ // Check
+ ASSERT_EQ(tc.annotations.size(), insts.size())
+ << "wrong number of annotation instructions";
+ auto inst_iter = insts.begin();
+ for (const std::string& expected_anno_inst : tc.annotations) {
+ EXPECT_EQ(expected_anno_inst, DisassembleInst(*inst_iter))
+ << "annotation instruction mismatch";
+ inst_iter++;
+ }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ TestCase, GetAnnotationsTest,
+ ::testing::ValuesIn(std::vector<GetAnnotationsTestCase>{
+ // empty
+ {"", 0, {}},
+ // basic
+ {
+ // code
+ "OpDecorate %1 Block "
+ "OpDecorate %1 RelaxedPrecision "
+ "%3 = OpTypeInt 32 0 "
+ "%1 = OpTypeStruct %3",
+ // id
+ 1,
+ // annotations
+ {
+ "OpDecorate %1 Block",
+ "OpDecorate %1 RelaxedPrecision",
+ },
+ },
+ // with debug instructions
+ {
+ // code
+ "OpName %1 \"struct_type\" "
+ "OpName %3 \"int_type\" "
+ "OpDecorate %1 Block "
+ "OpDecorate %1 RelaxedPrecision "
+ "%3 = OpTypeInt 32 0 "
+ "%1 = OpTypeStruct %3",
+ // id
+ 1,
+ // annotations
+ {
+ "OpDecorate %1 Block",
+ "OpDecorate %1 RelaxedPrecision",
+ },
+ },
+ // no annotations
+ {
+ // code
+ "OpName %1 \"struct_type\" "
+ "OpName %3 \"int_type\" "
+ "OpDecorate %1 Block "
+ "OpDecorate %1 RelaxedPrecision "
+ "%3 = OpTypeInt 32 0 "
+ "%1 = OpTypeStruct %3",
+ // id
+ 3,
+ // annotations
+ {},
+ },
+ // decoration group
+ {
+ // code
+ "OpDecorate %1 Block "
+ "OpDecorate %1 RelaxedPrecision "
+ "%1 = OpDecorationGroup "
+ "OpGroupDecorate %1 %2 %3 "
+ "%4 = OpTypeInt 32 0 "
+ "%2 = OpTypeStruct %4 "
+ "%3 = OpTypeStruct %4 %4",
+ // id
+ 3,
+ // annotations
+ {
+ "OpGroupDecorate %1 %2 %3",
+ },
+ },
+ // memeber decorate
+ {
+ // code
+ "OpMemberDecorate %1 0 RelaxedPrecision "
+ "%2 = OpTypeInt 32 0 "
+ "%1 = OpTypeStruct %2 %2",
+ // id
+ 1,
+ // annotations
+ {
+ "OpMemberDecorate %1 0 RelaxedPrecision",
+ },
+ },
+ }));
+
+using UpdateUsesTest = PassTest<::testing::Test>;
+
+TEST_F(UpdateUsesTest, KeepOldUses) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %main \"main\"",
+ "%void = OpTypeVoid",
+ "%4 = OpTypeFunction %void",
+ "%uint = OpTypeInt 32 0",
+ "%uint_5 = OpConstant %uint 5",
+ "%25 = OpConstant %uint 25",
+ "%main = OpFunction %void None %4",
+ "%8 = OpLabel",
+ "%9 = OpIMul %uint %uint_5 %uint_5",
+ "%10 = OpIMul %uint %9 %uint_5",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, JoinAllInsts(text),
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* def = def_use_mgr->GetDef(9);
+ Instruction* use = def_use_mgr->GetDef(10);
+ def->SetOpcode(SpvOpCopyObject);
+ def->SetInOperands({{SPV_OPERAND_TYPE_ID, {25}}});
+ context->UpdateDefUse(def);
+
+ auto users = def_use_mgr->id_to_users();
+ UserEntry entry = {def, use};
+ EXPECT_THAT(users, Contains(entry));
+}
+// clang-format on
+
+} // namespace
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/CMakeLists.txt b/test/opt/dominator_tree/CMakeLists.txt
new file mode 100644
index 0000000..813d628
--- /dev/null
+++ b/test/opt/dominator_tree/CMakeLists.txt
@@ -0,0 +1,31 @@
+# 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.
+
+
+add_spvtools_unittest(TARGET dominator_analysis
+ SRCS ../function_utils.h
+ common_dominators.cpp
+ generated.cpp
+ nested_ifs.cpp
+ nested_ifs_post.cpp
+ nested_loops.cpp
+ nested_loops_with_unreachables.cpp
+ post.cpp
+ simple.cpp
+ switch_case_fallthrough.cpp
+ unreachable_for.cpp
+ unreachable_for_post.cpp
+ LIBS SPIRV-Tools-opt
+ PCH_FILE pch_test_opt_dom
+)
diff --git a/test/opt/dominator_tree/common_dominators.cpp b/test/opt/dominator_tree/common_dominators.cpp
new file mode 100644
index 0000000..dfa03e9
--- /dev/null
+++ b/test/opt/dominator_tree/common_dominators.cpp
@@ -0,0 +1,151 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using CommonDominatorsTest = ::testing::Test;
+
+const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %5
+%5 = OpLabel
+OpBranchConditional %true %3 %4
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %true %7 %8
+%7 = OpLabel
+OpBranch %6
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpBranch %6
+%6 = OpLabel
+OpBranch %10
+%11 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+BasicBlock* GetBlock(uint32_t id, std::unique_ptr<IRContext>& context) {
+ return context->get_instr_block(context->get_def_use_mgr()->GetDef(id));
+}
+
+TEST(CommonDominatorsTest, SameBlock) {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, context);
+
+ DominatorAnalysis* analysis =
+ context->GetDominatorAnalysis(&*context->module()->begin());
+
+ for (auto& block : *context->module()->begin()) {
+ EXPECT_EQ(&block, analysis->CommonDominator(&block, &block));
+ }
+}
+
+TEST(CommonDominatorsTest, ParentAndChild) {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, context);
+
+ DominatorAnalysis* analysis =
+ context->GetDominatorAnalysis(&*context->module()->begin());
+
+ EXPECT_EQ(
+ GetBlock(1u, context),
+ analysis->CommonDominator(GetBlock(1u, context), GetBlock(2u, context)));
+ EXPECT_EQ(
+ GetBlock(2u, context),
+ analysis->CommonDominator(GetBlock(2u, context), GetBlock(5u, context)));
+ EXPECT_EQ(
+ GetBlock(1u, context),
+ analysis->CommonDominator(GetBlock(1u, context), GetBlock(5u, context)));
+}
+
+TEST(CommonDominatorsTest, BranchSplit) {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, context);
+
+ DominatorAnalysis* analysis =
+ context->GetDominatorAnalysis(&*context->module()->begin());
+
+ EXPECT_EQ(
+ GetBlock(3u, context),
+ analysis->CommonDominator(GetBlock(7u, context), GetBlock(8u, context)));
+ EXPECT_EQ(
+ GetBlock(3u, context),
+ analysis->CommonDominator(GetBlock(7u, context), GetBlock(9u, context)));
+}
+
+TEST(CommonDominatorsTest, LoopContinueAndMerge) {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, context);
+
+ DominatorAnalysis* analysis =
+ context->GetDominatorAnalysis(&*context->module()->begin());
+
+ EXPECT_EQ(
+ GetBlock(5u, context),
+ analysis->CommonDominator(GetBlock(3u, context), GetBlock(4u, context)));
+}
+
+TEST(CommonDominatorsTest, NoCommonDominator) {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, context);
+
+ DominatorAnalysis* analysis =
+ context->GetDominatorAnalysis(&*context->module()->begin());
+
+ EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(10u, context),
+ GetBlock(11u, context)));
+ EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(11u, context),
+ GetBlock(6u, context)));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/generated.cpp b/test/opt/dominator_tree/generated.cpp
new file mode 100644
index 0000000..43b723e
--- /dev/null
+++ b/test/opt/dominator_tree/generated.cpp
@@ -0,0 +1,900 @@
+// 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 <array>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/iterator.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+// Check that x dominates y, and
+// if x != y then
+// x strictly dominates y and
+// y does not dominate x and
+// y does not strictly dominate x
+// if x == x then
+// x does not strictly dominate itself
+void check_dominance(const DominatorAnalysisBase& dom_tree, const Function* fn,
+ uint32_t x, uint32_t y) {
+ SCOPED_TRACE("Check dominance properties for Basic Block " +
+ std::to_string(x) + " and " + std::to_string(y));
+ EXPECT_TRUE(dom_tree.Dominates(spvtest::GetBasicBlock(fn, x),
+ spvtest::GetBasicBlock(fn, y)));
+ EXPECT_TRUE(dom_tree.Dominates(x, y));
+ if (x == y) {
+ EXPECT_FALSE(dom_tree.StrictlyDominates(x, x));
+ } else {
+ EXPECT_TRUE(dom_tree.StrictlyDominates(x, y));
+ EXPECT_FALSE(dom_tree.Dominates(y, x));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(y, x));
+ }
+}
+
+// Check that x does not dominates y and vise versa
+void check_no_dominance(const DominatorAnalysisBase& dom_tree,
+ const Function* fn, uint32_t x, uint32_t y) {
+ SCOPED_TRACE("Check no domination for Basic Block " + std::to_string(x) +
+ " and " + std::to_string(y));
+ EXPECT_FALSE(dom_tree.Dominates(spvtest::GetBasicBlock(fn, x),
+ spvtest::GetBasicBlock(fn, y)));
+ EXPECT_FALSE(dom_tree.Dominates(x, y));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(spvtest::GetBasicBlock(fn, x),
+ spvtest::GetBasicBlock(fn, y)));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(x, y));
+
+ EXPECT_FALSE(dom_tree.Dominates(spvtest::GetBasicBlock(fn, y),
+ spvtest::GetBasicBlock(fn, x)));
+ EXPECT_FALSE(dom_tree.Dominates(y, x));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(spvtest::GetBasicBlock(fn, y),
+ spvtest::GetBasicBlock(fn, x)));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(y, x));
+}
+
+TEST_F(PassClassTest, DominatorSimpleCFG) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeBool
+ %5 = OpTypeInt 32 0
+ %6 = OpConstant %5 0
+ %7 = OpConstantFalse %4
+ %8 = OpConstantTrue %4
+ %9 = OpConstant %5 1
+ %1 = OpFunction %2 None %3
+ %10 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ OpSwitch %6 %12 1 %13
+ %12 = OpLabel
+ OpBranch %14
+ %13 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ OpBranchConditional %8 %11 %15
+ %15 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* fn = spvtest::GetFunction(module, 1);
+ const BasicBlock* entry = spvtest::GetBasicBlock(fn, 10);
+ EXPECT_EQ(entry, fn->entry().get())
+ << "The entry node is not the expected one";
+
+ // Test normal dominator tree
+ {
+ DominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block());
+ EXPECT_TRUE(
+ dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id()));
+
+ // (strict) dominance checks
+ for (uint32_t id : {10, 11, 12, 13, 14, 15})
+ check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 10, 11);
+ check_dominance(dom_tree, fn, 10, 12);
+ check_dominance(dom_tree, fn, 10, 13);
+ check_dominance(dom_tree, fn, 10, 14);
+ check_dominance(dom_tree, fn, 10, 15);
+
+ check_dominance(dom_tree, fn, 11, 12);
+ check_dominance(dom_tree, fn, 11, 13);
+ check_dominance(dom_tree, fn, 11, 14);
+ check_dominance(dom_tree, fn, 11, 15);
+
+ check_dominance(dom_tree, fn, 14, 15);
+
+ check_no_dominance(dom_tree, fn, 12, 13);
+ check_no_dominance(dom_tree, fn, 12, 14);
+ check_no_dominance(dom_tree, fn, 13, 14);
+
+ // check with some invalid inputs
+ EXPECT_FALSE(dom_tree.Dominates(nullptr, entry));
+ EXPECT_FALSE(dom_tree.Dominates(entry, nullptr));
+ EXPECT_FALSE(dom_tree.Dominates(static_cast<BasicBlock*>(nullptr),
+ static_cast<BasicBlock*>(nullptr)));
+ EXPECT_FALSE(dom_tree.Dominates(10, 1));
+ EXPECT_FALSE(dom_tree.Dominates(1, 10));
+ EXPECT_FALSE(dom_tree.Dominates(1, 1));
+
+ EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, entry));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(entry, nullptr));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, nullptr));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(10, 1));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(1, 10));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(1, 1));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr);
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block());
+ EXPECT_EQ(dom_tree.ImmediateDominator(nullptr), nullptr);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 10));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)),
+ spvtest::GetBasicBlock(fn, 14));
+ }
+
+ // Test post dominator tree
+ {
+ PostDominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block());
+ EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 15));
+
+ // (strict) dominance checks
+ for (uint32_t id : {10, 11, 12, 13, 14, 15})
+ check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 14, 10);
+ check_dominance(dom_tree, fn, 14, 11);
+ check_dominance(dom_tree, fn, 14, 12);
+ check_dominance(dom_tree, fn, 14, 13);
+
+ check_dominance(dom_tree, fn, 15, 10);
+ check_dominance(dom_tree, fn, 15, 11);
+ check_dominance(dom_tree, fn, 15, 12);
+ check_dominance(dom_tree, fn, 15, 13);
+ check_dominance(dom_tree, fn, 15, 14);
+
+ check_no_dominance(dom_tree, fn, 13, 12);
+ check_no_dominance(dom_tree, fn, 12, 11);
+ check_no_dominance(dom_tree, fn, 13, 11);
+
+ // check with some invalid inputs
+ EXPECT_FALSE(dom_tree.Dominates(nullptr, entry));
+ EXPECT_FALSE(dom_tree.Dominates(entry, nullptr));
+ EXPECT_FALSE(dom_tree.Dominates(static_cast<BasicBlock*>(nullptr),
+ static_cast<BasicBlock*>(nullptr)));
+ EXPECT_FALSE(dom_tree.Dominates(10, 1));
+ EXPECT_FALSE(dom_tree.Dominates(1, 10));
+ EXPECT_FALSE(dom_tree.Dominates(1, 1));
+
+ EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, entry));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(entry, nullptr));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(nullptr, nullptr));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(10, 1));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(1, 10));
+ EXPECT_FALSE(dom_tree.StrictlyDominates(1, 1));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(nullptr), nullptr);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 14));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ spvtest::GetBasicBlock(fn, 14));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)),
+ spvtest::GetBasicBlock(fn, 14));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)),
+ spvtest::GetBasicBlock(fn, 15));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)),
+ cfg.pseudo_exit_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr);
+ }
+}
+
+TEST_F(PassClassTest, DominatorIrreducibleCFG) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeBool
+ %5 = OpTypeInt 32 0
+ %6 = OpConstantFalse %4
+ %7 = OpConstantTrue %4
+ %1 = OpFunction %2 None %3
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpBranchConditional %7 %10 %11
+ %10 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ OpBranchConditional %7 %10 %12
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* fn = spvtest::GetFunction(module, 1);
+
+ const BasicBlock* entry = spvtest::GetBasicBlock(fn, 8);
+ EXPECT_EQ(entry, fn->entry().get())
+ << "The entry node is not the expected one";
+
+ // Check normal dominator tree
+ {
+ DominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block());
+ EXPECT_TRUE(
+ dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id()));
+
+ // (strict) dominance checks
+ for (uint32_t id : {8, 9, 10, 11, 12})
+ check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 8, 9);
+ check_dominance(dom_tree, fn, 8, 10);
+ check_dominance(dom_tree, fn, 8, 11);
+ check_dominance(dom_tree, fn, 8, 12);
+
+ check_dominance(dom_tree, fn, 9, 10);
+ check_dominance(dom_tree, fn, 9, 11);
+ check_dominance(dom_tree, fn, 9, 12);
+
+ check_dominance(dom_tree, fn, 11, 12);
+
+ check_no_dominance(dom_tree, fn, 10, 11);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr);
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)),
+ spvtest::GetBasicBlock(fn, 8));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)),
+ spvtest::GetBasicBlock(fn, 9));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 9));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ spvtest::GetBasicBlock(fn, 11));
+ }
+
+ // Check post dominator tree
+ {
+ PostDominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block());
+ EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 12));
+
+ // (strict) dominance checks
+ for (uint32_t id : {8, 9, 10, 11, 12})
+ check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 12, 8);
+ check_dominance(dom_tree, fn, 12, 10);
+ check_dominance(dom_tree, fn, 12, 11);
+ check_dominance(dom_tree, fn, 12, 12);
+
+ check_dominance(dom_tree, fn, 11, 8);
+ check_dominance(dom_tree, fn, 11, 9);
+ check_dominance(dom_tree, fn, 11, 10);
+
+ check_dominance(dom_tree, fn, 9, 8);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry),
+ spvtest::GetBasicBlock(fn, 9));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 12));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ cfg.pseudo_exit_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr);
+ }
+}
+
+TEST_F(PassClassTest, DominatorLoopToSelf) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeBool
+ %5 = OpTypeInt 32 0
+ %6 = OpConstant %5 0
+ %7 = OpConstantFalse %4
+ %8 = OpConstantTrue %4
+ %9 = OpConstant %5 1
+ %1 = OpFunction %2 None %3
+ %10 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ OpSwitch %6 %12 1 %11
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* fn = spvtest::GetFunction(module, 1);
+
+ const BasicBlock* entry = spvtest::GetBasicBlock(fn, 10);
+ EXPECT_EQ(entry, fn->entry().get())
+ << "The entry node is not the expected one";
+
+ // Check normal dominator tree
+ {
+ DominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block());
+ EXPECT_TRUE(
+ dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id()));
+
+ // (strict) dominance checks
+ for (uint32_t id : {10, 11, 12}) check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 10, 11);
+ check_dominance(dom_tree, fn, 10, 12);
+ check_dominance(dom_tree, fn, 11, 12);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr);
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 10));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ spvtest::GetBasicBlock(fn, 11));
+
+ std::array<uint32_t, 3> node_order = {{10, 11, 12}};
+ {
+ // Test dominator tree iteration order.
+ DominatorTree::iterator node_it = dom_tree.GetDomTree().begin();
+ DominatorTree::iterator node_end = dom_tree.GetDomTree().end();
+ for (uint32_t id : node_order) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ {
+ // Same as above, but with const iterators.
+ DominatorTree::const_iterator node_it = dom_tree.GetDomTree().cbegin();
+ DominatorTree::const_iterator node_end = dom_tree.GetDomTree().cend();
+ for (uint32_t id : node_order) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ {
+ // Test dominator tree iteration order.
+ DominatorTree::post_iterator node_it = dom_tree.GetDomTree().post_begin();
+ DominatorTree::post_iterator node_end = dom_tree.GetDomTree().post_end();
+ for (uint32_t id : make_range(node_order.rbegin(), node_order.rend())) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ {
+ // Same as above, but with const iterators.
+ DominatorTree::const_post_iterator node_it =
+ dom_tree.GetDomTree().post_cbegin();
+ DominatorTree::const_post_iterator node_end =
+ dom_tree.GetDomTree().post_cend();
+ for (uint32_t id : make_range(node_order.rbegin(), node_order.rend())) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ }
+
+ // Check post dominator tree
+ {
+ PostDominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block());
+ EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 12));
+
+ // (strict) dominance checks
+ for (uint32_t id : {10, 11, 12}) check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 12, 10);
+ check_dominance(dom_tree, fn, 12, 11);
+ check_dominance(dom_tree, fn, 12, 12);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry),
+ spvtest::GetBasicBlock(fn, 11));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 12));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ cfg.pseudo_exit_block());
+
+ std::array<uint32_t, 3> node_order = {{12, 11, 10}};
+ {
+ // Test dominator tree iteration order.
+ DominatorTree::iterator node_it = tree.begin();
+ DominatorTree::iterator node_end = tree.end();
+ for (uint32_t id : node_order) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ {
+ // Same as above, but with const iterators.
+ DominatorTree::const_iterator node_it = tree.cbegin();
+ DominatorTree::const_iterator node_end = tree.cend();
+ for (uint32_t id : node_order) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ {
+ // Test dominator tree iteration order.
+ DominatorTree::post_iterator node_it = dom_tree.GetDomTree().post_begin();
+ DominatorTree::post_iterator node_end = dom_tree.GetDomTree().post_end();
+ for (uint32_t id : make_range(node_order.rbegin(), node_order.rend())) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ {
+ // Same as above, but with const iterators.
+ DominatorTree::const_post_iterator node_it =
+ dom_tree.GetDomTree().post_cbegin();
+ DominatorTree::const_post_iterator node_end =
+ dom_tree.GetDomTree().post_cend();
+ for (uint32_t id : make_range(node_order.rbegin(), node_order.rend())) {
+ EXPECT_NE(node_it, node_end);
+ EXPECT_EQ(node_it->id(), id);
+ node_it++;
+ }
+ EXPECT_EQ(node_it, node_end);
+ }
+ }
+}
+
+TEST_F(PassClassTest, DominatorUnreachableInLoop) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeBool
+ %5 = OpTypeInt 32 0
+ %6 = OpConstant %5 0
+ %7 = OpConstantFalse %4
+ %8 = OpConstantTrue %4
+ %9 = OpConstant %5 1
+ %1 = OpFunction %2 None %3
+ %10 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ OpSwitch %6 %12 1 %13
+ %12 = OpLabel
+ OpBranch %14
+ %13 = OpLabel
+ OpUnreachable
+ %14 = OpLabel
+ OpBranchConditional %8 %11 %15
+ %15 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* fn = spvtest::GetFunction(module, 1);
+
+ const BasicBlock* entry = spvtest::GetBasicBlock(fn, 10);
+ EXPECT_EQ(entry, fn->entry().get())
+ << "The entry node is not the expected one";
+
+ // Check normal dominator tree
+ {
+ DominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block());
+ EXPECT_TRUE(
+ dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id()));
+
+ // (strict) dominance checks
+ for (uint32_t id : {10, 11, 12, 13, 14, 15})
+ check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 10, 11);
+ check_dominance(dom_tree, fn, 10, 13);
+ check_dominance(dom_tree, fn, 10, 12);
+ check_dominance(dom_tree, fn, 10, 14);
+ check_dominance(dom_tree, fn, 10, 15);
+
+ check_dominance(dom_tree, fn, 11, 12);
+ check_dominance(dom_tree, fn, 11, 13);
+ check_dominance(dom_tree, fn, 11, 14);
+ check_dominance(dom_tree, fn, 11, 15);
+
+ check_dominance(dom_tree, fn, 12, 14);
+ check_dominance(dom_tree, fn, 12, 15);
+
+ check_dominance(dom_tree, fn, 14, 15);
+
+ check_no_dominance(dom_tree, fn, 13, 12);
+ check_no_dominance(dom_tree, fn, 13, 14);
+ check_no_dominance(dom_tree, fn, 13, 15);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr);
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 10));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)),
+ spvtest::GetBasicBlock(fn, 12));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)),
+ spvtest::GetBasicBlock(fn, 14));
+ }
+
+ // Check post dominator tree.
+ {
+ PostDominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // (strict) dominance checks.
+ for (uint32_t id : {10, 11, 12, 13, 14, 15})
+ check_dominance(dom_tree, fn, id, id);
+
+ check_no_dominance(dom_tree, fn, 15, 10);
+ check_no_dominance(dom_tree, fn, 15, 11);
+ check_no_dominance(dom_tree, fn, 15, 12);
+ check_no_dominance(dom_tree, fn, 15, 13);
+ check_no_dominance(dom_tree, fn, 15, 14);
+
+ check_dominance(dom_tree, fn, 14, 12);
+
+ check_no_dominance(dom_tree, fn, 13, 10);
+ check_no_dominance(dom_tree, fn, 13, 11);
+ check_no_dominance(dom_tree, fn, 13, 12);
+ check_no_dominance(dom_tree, fn, 13, 14);
+ check_no_dominance(dom_tree, fn, 13, 15);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ spvtest::GetBasicBlock(fn, 14));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 15)),
+ cfg.pseudo_exit_block());
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)),
+ cfg.pseudo_exit_block());
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 14)),
+ cfg.pseudo_exit_block());
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ cfg.pseudo_exit_block());
+ }
+}
+
+TEST_F(PassClassTest, DominatorInfinitLoop) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeBool
+ %5 = OpTypeInt 32 0
+ %6 = OpConstant %5 0
+ %7 = OpConstantFalse %4
+ %8 = OpConstantTrue %4
+ %9 = OpConstant %5 1
+ %1 = OpFunction %2 None %3
+ %10 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ OpSwitch %6 %12 1 %13
+ %12 = OpLabel
+ OpReturn
+ %13 = OpLabel
+ OpBranch %13
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* fn = spvtest::GetFunction(module, 1);
+
+ const BasicBlock* entry = spvtest::GetBasicBlock(fn, 10);
+ EXPECT_EQ(entry, fn->entry().get())
+ << "The entry node is not the expected one";
+ // Check normal dominator tree
+ {
+ DominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block());
+ EXPECT_TRUE(
+ dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id()));
+
+ // (strict) dominance checks
+ for (uint32_t id : {10, 11, 12, 13}) check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 10, 11);
+ check_dominance(dom_tree, fn, 10, 12);
+ check_dominance(dom_tree, fn, 10, 13);
+
+ check_dominance(dom_tree, fn, 11, 12);
+ check_dominance(dom_tree, fn, 11, 13);
+
+ check_no_dominance(dom_tree, fn, 13, 12);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr);
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 10));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ spvtest::GetBasicBlock(fn, 11));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 13)),
+ spvtest::GetBasicBlock(fn, 11));
+ }
+
+ // Check post dominator tree
+ {
+ PostDominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block());
+ EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 12));
+
+ // (strict) dominance checks
+ for (uint32_t id : {10, 11, 12}) check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 12, 11);
+ check_dominance(dom_tree, fn, 12, 10);
+
+ // 13 should be completely out of tree as it's unreachable from exit nodes
+ check_no_dominance(dom_tree, fn, 12, 13);
+ check_no_dominance(dom_tree, fn, 11, 13);
+ check_no_dominance(dom_tree, fn, 10, 13);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 12)),
+ cfg.pseudo_exit_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)),
+ spvtest::GetBasicBlock(fn, 11));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 11)),
+ spvtest::GetBasicBlock(fn, 12));
+ }
+}
+
+TEST_F(PassClassTest, DominatorUnreachableFromEntry) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Addresses
+ OpCapability Kernel
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %1 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %4 = OpTypeBool
+ %5 = OpTypeInt 32 0
+ %6 = OpConstantFalse %4
+ %7 = OpConstantTrue %4
+ %1 = OpFunction %2 None %3
+ %8 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpReturn
+ %10 = OpLabel
+ OpBranch %9
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_0, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* fn = spvtest::GetFunction(module, 1);
+
+ const BasicBlock* entry = spvtest::GetBasicBlock(fn, 8);
+ EXPECT_EQ(entry, fn->entry().get())
+ << "The entry node is not the expected one";
+
+ // Check dominator tree
+ {
+ DominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block());
+ EXPECT_TRUE(
+ dom_tree.Dominates(cfg.pseudo_entry_block()->id(), entry->id()));
+
+ // (strict) dominance checks
+ for (uint32_t id : {8, 9}) check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 8, 9);
+
+ check_no_dominance(dom_tree, fn, 10, 8);
+ check_no_dominance(dom_tree, fn, 10, 9);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_entry_block()), nullptr);
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry), cfg.pseudo_entry_block());
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)),
+ spvtest::GetBasicBlock(fn, 8));
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)),
+ nullptr);
+ }
+
+ // Check post dominator tree
+ {
+ PostDominatorAnalysis dom_tree;
+ const CFG& cfg = *context->cfg();
+ dom_tree.InitializeTree(cfg, fn);
+
+ // Inspect the actual tree
+ DominatorTree& tree = dom_tree.GetDomTree();
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_exit_block());
+ EXPECT_TRUE(dom_tree.Dominates(cfg.pseudo_exit_block()->id(), 9));
+
+ // (strict) dominance checks
+ for (uint32_t id : {8, 9, 10}) check_dominance(dom_tree, fn, id, id);
+
+ check_dominance(dom_tree, fn, 9, 8);
+ check_dominance(dom_tree, fn, 9, 10);
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(entry),
+ spvtest::GetBasicBlock(fn, 9));
+
+ EXPECT_EQ(dom_tree.ImmediateDominator(cfg.pseudo_exit_block()), nullptr);
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 9)),
+ cfg.pseudo_exit_block());
+ EXPECT_EQ(dom_tree.ImmediateDominator(spvtest::GetBasicBlock(fn, 10)),
+ spvtest::GetBasicBlock(fn, 9));
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/nested_ifs.cpp b/test/opt/dominator_tree/nested_ifs.cpp
new file mode 100644
index 0000000..0552b75
--- /dev/null
+++ b/test/opt/dominator_tree/nested_ifs.cpp
@@ -0,0 +1,153 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 v;
+void main(){
+ if (true) {
+ if (true) {
+ v = vec4(1,1,1,1);
+ } else {
+ v = vec4(2,2,2,2);
+ }
+ } else {
+ if (true) {
+ v = vec4(3,3,3,3);
+ } else {
+ v = vec4(4,4,4,4);
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, UnreachableNestedIfs) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %15
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %4 "main"
+ OpName %15 "v"
+ OpDecorate %15 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeBool
+ %7 = OpConstantTrue %6
+ %12 = OpTypeFloat 32
+ %13 = OpTypeVector %12 4
+ %14 = OpTypePointer Output %13
+ %15 = OpVariable %14 Output
+ %16 = OpConstant %12 1
+ %17 = OpConstantComposite %13 %16 %16 %16 %16
+ %19 = OpConstant %12 2
+ %20 = OpConstantComposite %13 %19 %19 %19 %19
+ %24 = OpConstant %12 3
+ %25 = OpConstantComposite %13 %24 %24 %24 %24
+ %27 = OpConstant %12 4
+ %28 = OpConstantComposite %13 %27 %27 %27 %27
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %7 %8 %21
+ %8 = OpLabel
+ OpSelectionMerge %11 None
+ OpBranchConditional %7 %10 %18
+ %10 = OpLabel
+ OpStore %15 %17
+ OpBranch %11
+ %18 = OpLabel
+ OpStore %15 %20
+ OpBranch %11
+ %11 = OpLabel
+ OpBranch %9
+ %21 = OpLabel
+ OpSelectionMerge %23 None
+ OpBranchConditional %7 %22 %26
+ %22 = OpLabel
+ OpStore %15 %25
+ OpBranch %23
+ %26 = OpLabel
+ OpStore %15 %28
+ OpBranch %23
+ %23 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+
+ DominatorAnalysis* analysis = context->GetDominatorAnalysis(f);
+
+ EXPECT_TRUE(analysis->Dominates(5, 8));
+ EXPECT_TRUE(analysis->Dominates(5, 9));
+ EXPECT_TRUE(analysis->Dominates(5, 21));
+ EXPECT_TRUE(analysis->Dominates(5, 18));
+ EXPECT_TRUE(analysis->Dominates(5, 10));
+ EXPECT_TRUE(analysis->Dominates(5, 11));
+ EXPECT_TRUE(analysis->Dominates(5, 23));
+ EXPECT_TRUE(analysis->Dominates(5, 22));
+ EXPECT_TRUE(analysis->Dominates(5, 26));
+ EXPECT_TRUE(analysis->Dominates(8, 18));
+ EXPECT_TRUE(analysis->Dominates(8, 10));
+ EXPECT_TRUE(analysis->Dominates(8, 11));
+ EXPECT_TRUE(analysis->Dominates(21, 23));
+ EXPECT_TRUE(analysis->Dominates(21, 22));
+ EXPECT_TRUE(analysis->Dominates(21, 26));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 8));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 9));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 21));
+ EXPECT_TRUE(analysis->StrictlyDominates(8, 18));
+ EXPECT_TRUE(analysis->StrictlyDominates(8, 10));
+ EXPECT_TRUE(analysis->StrictlyDominates(8, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(21, 23));
+ EXPECT_TRUE(analysis->StrictlyDominates(21, 22));
+ EXPECT_TRUE(analysis->StrictlyDominates(21, 26));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/nested_ifs_post.cpp b/test/opt/dominator_tree/nested_ifs_post.cpp
new file mode 100644
index 0000000..ad759df
--- /dev/null
+++ b/test/opt/dominator_tree/nested_ifs_post.cpp
@@ -0,0 +1,156 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 v;
+void main(){
+ if (true) {
+ if (true) {
+ v = vec4(1,1,1,1);
+ } else {
+ v = vec4(2,2,2,2);
+ }
+ } else {
+ if (true) {
+ v = vec4(3,3,3,3);
+ } else {
+ v = vec4(4,4,4,4);
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, UnreachableNestedIfs) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %15
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %4 "main"
+ OpName %15 "v"
+ OpDecorate %15 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeBool
+ %7 = OpConstantTrue %6
+ %12 = OpTypeFloat 32
+ %13 = OpTypeVector %12 4
+ %14 = OpTypePointer Output %13
+ %15 = OpVariable %14 Output
+ %16 = OpConstant %12 1
+ %17 = OpConstantComposite %13 %16 %16 %16 %16
+ %19 = OpConstant %12 2
+ %20 = OpConstantComposite %13 %19 %19 %19 %19
+ %24 = OpConstant %12 3
+ %25 = OpConstantComposite %13 %24 %24 %24 %24
+ %27 = OpConstant %12 4
+ %28 = OpConstantComposite %13 %27 %27 %27 %27
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpSelectionMerge %9 None
+ OpBranchConditional %7 %8 %21
+ %8 = OpLabel
+ OpSelectionMerge %11 None
+ OpBranchConditional %7 %10 %18
+ %10 = OpLabel
+ OpStore %15 %17
+ OpBranch %11
+ %18 = OpLabel
+ OpStore %15 %20
+ OpBranch %11
+ %11 = OpLabel
+ OpBranch %9
+ %21 = OpLabel
+ OpSelectionMerge %23 None
+ OpBranchConditional %7 %22 %26
+ %22 = OpLabel
+ OpStore %15 %25
+ OpBranch %23
+ %26 = OpLabel
+ OpStore %15 %28
+ OpBranch %23
+ %23 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+
+ PostDominatorAnalysis* analysis = context->GetPostDominatorAnalysis(f);
+
+ EXPECT_TRUE(analysis->Dominates(5, 5));
+ EXPECT_TRUE(analysis->Dominates(8, 8));
+ EXPECT_TRUE(analysis->Dominates(9, 9));
+ EXPECT_TRUE(analysis->Dominates(10, 10));
+ EXPECT_TRUE(analysis->Dominates(11, 11));
+ EXPECT_TRUE(analysis->Dominates(18, 18));
+ EXPECT_TRUE(analysis->Dominates(21, 21));
+ EXPECT_TRUE(analysis->Dominates(22, 22));
+ EXPECT_TRUE(analysis->Dominates(23, 23));
+ EXPECT_TRUE(analysis->Dominates(26, 26));
+ EXPECT_TRUE(analysis->Dominates(9, 5));
+ EXPECT_TRUE(analysis->Dominates(9, 11));
+ EXPECT_TRUE(analysis->Dominates(9, 23));
+ EXPECT_TRUE(analysis->Dominates(11, 10));
+ EXPECT_TRUE(analysis->Dominates(11, 18));
+ EXPECT_TRUE(analysis->Dominates(11, 8));
+ EXPECT_TRUE(analysis->Dominates(23, 22));
+ EXPECT_TRUE(analysis->Dominates(23, 26));
+ EXPECT_TRUE(analysis->Dominates(23, 21));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(9, 5));
+ EXPECT_TRUE(analysis->StrictlyDominates(9, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(9, 23));
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 10));
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 18));
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 8));
+ EXPECT_TRUE(analysis->StrictlyDominates(23, 22));
+ EXPECT_TRUE(analysis->StrictlyDominates(23, 26));
+ EXPECT_TRUE(analysis->StrictlyDominates(23, 21));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/nested_loops.cpp b/test/opt/dominator_tree/nested_loops.cpp
new file mode 100644
index 0000000..7d03937
--- /dev/null
+++ b/test/opt/dominator_tree/nested_loops.cpp
@@ -0,0 +1,433 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Generated from the following GLSL
+#version 440 core
+layout(location = 0) out vec4 v;
+layout(location = 1) in vec4 in_val;
+void main() {
+ for (int i = 0; i < in_val.x; ++i) {
+ for (int j = 0; j < in_val.y; j++) {
+ }
+ }
+ for (int i = 0; i < in_val.x; ++i) {
+ for (int j = 0; j < in_val.y; j++) {
+ }
+ if (in_val.z == in_val.w) {
+ break;
+ }
+ }
+ int i = 0;
+ while (i < in_val.x) {
+ ++i;
+ for (int j = 0; j < 1; j++) {
+ for (int k = 0; k < 1; k++) {
+ }
+ }
+ }
+ i = 0;
+ while (i < in_val.x) {
+ ++i;
+ if (in_val.z == in_val.w) {
+ continue;
+ }
+ for (int j = 0; j < 1; j++) {
+ for (int k = 0; k < 1; k++) {
+ }
+ if (in_val.z == in_val.w) {
+ break;
+ }
+ }
+ }
+ v = vec4(1,1,1,1);
+}
+*/
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %20 %163
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %20 "in_val"
+ OpName %28 "j"
+ OpName %45 "i"
+ OpName %56 "j"
+ OpName %81 "i"
+ OpName %94 "j"
+ OpName %102 "k"
+ OpName %134 "j"
+ OpName %142 "k"
+ OpName %163 "v"
+ OpDecorate %20 Location 1
+ OpDecorate %163 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpTypeFloat 32
+ %18 = OpTypeVector %16 4
+ %19 = OpTypePointer Input %18
+ %20 = OpVariable %19 Input
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 0
+ %23 = OpTypePointer Input %16
+ %26 = OpTypeBool
+ %36 = OpConstant %21 1
+ %41 = OpConstant %6 1
+ %69 = OpConstant %21 2
+ %72 = OpConstant %21 3
+ %162 = OpTypePointer Output %18
+ %163 = OpVariable %162 Output
+ %164 = OpConstant %16 1
+ %165 = OpConstantComposite %18 %164 %164 %164 %164
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %28 = OpVariable %7 Function
+ %45 = OpVariable %7 Function
+ %56 = OpVariable %7 Function
+ %81 = OpVariable %7 Function
+ %94 = OpVariable %7 Function
+ %102 = OpVariable %7 Function
+ %134 = OpVariable %7 Function
+ %142 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %17 = OpConvertSToF %16 %15
+ %24 = OpAccessChain %23 %20 %22
+ %25 = OpLoad %16 %24
+ %27 = OpFOrdLessThan %26 %17 %25
+ OpBranchConditional %27 %11 %12
+ %11 = OpLabel
+ OpStore %28 %9
+ OpBranch %29
+ %29 = OpLabel
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+ %34 = OpLoad %6 %28
+ %35 = OpConvertSToF %16 %34
+ %37 = OpAccessChain %23 %20 %36
+ %38 = OpLoad %16 %37
+ %39 = OpFOrdLessThan %26 %35 %38
+ OpBranchConditional %39 %30 %31
+ %30 = OpLabel
+ OpBranch %32
+ %32 = OpLabel
+ %40 = OpLoad %6 %28
+ %42 = OpIAdd %6 %40 %41
+ OpStore %28 %42
+ OpBranch %29
+ %31 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %43 = OpLoad %6 %8
+ %44 = OpIAdd %6 %43 %41
+ OpStore %8 %44
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %45 %9
+ OpBranch %46
+ %46 = OpLabel
+ OpLoopMerge %48 %49 None
+ OpBranch %50
+ %50 = OpLabel
+ %51 = OpLoad %6 %45
+ %52 = OpConvertSToF %16 %51
+ %53 = OpAccessChain %23 %20 %22
+ %54 = OpLoad %16 %53
+ %55 = OpFOrdLessThan %26 %52 %54
+ OpBranchConditional %55 %47 %48
+ %47 = OpLabel
+ OpStore %56 %9
+ OpBranch %57
+ %57 = OpLabel
+ OpLoopMerge %59 %60 None
+ OpBranch %61
+ %61 = OpLabel
+ %62 = OpLoad %6 %56
+ %63 = OpConvertSToF %16 %62
+ %64 = OpAccessChain %23 %20 %36
+ %65 = OpLoad %16 %64
+ %66 = OpFOrdLessThan %26 %63 %65
+ OpBranchConditional %66 %58 %59
+ %58 = OpLabel
+ OpBranch %60
+ %60 = OpLabel
+ %67 = OpLoad %6 %56
+ %68 = OpIAdd %6 %67 %41
+ OpStore %56 %68
+ OpBranch %57
+ %59 = OpLabel
+ %70 = OpAccessChain %23 %20 %69
+ %71 = OpLoad %16 %70
+ %73 = OpAccessChain %23 %20 %72
+ %74 = OpLoad %16 %73
+ %75 = OpFOrdEqual %26 %71 %74
+ OpSelectionMerge %77 None
+ OpBranchConditional %75 %76 %77
+ %76 = OpLabel
+ OpBranch %48
+ %77 = OpLabel
+ OpBranch %49
+ %49 = OpLabel
+ %79 = OpLoad %6 %45
+ %80 = OpIAdd %6 %79 %41
+ OpStore %45 %80
+ OpBranch %46
+ %48 = OpLabel
+ OpStore %81 %9
+ OpBranch %82
+ %82 = OpLabel
+ OpLoopMerge %84 %85 None
+ OpBranch %86
+ %86 = OpLabel
+ %87 = OpLoad %6 %81
+ %88 = OpConvertSToF %16 %87
+ %89 = OpAccessChain %23 %20 %22
+ %90 = OpLoad %16 %89
+ %91 = OpFOrdLessThan %26 %88 %90
+ OpBranchConditional %91 %83 %84
+ %83 = OpLabel
+ %92 = OpLoad %6 %81
+ %93 = OpIAdd %6 %92 %41
+ OpStore %81 %93
+ OpStore %94 %9
+ OpBranch %95
+ %95 = OpLabel
+ OpLoopMerge %97 %98 None
+ OpBranch %99
+ %99 = OpLabel
+ %100 = OpLoad %6 %94
+ %101 = OpSLessThan %26 %100 %41
+ OpBranchConditional %101 %96 %97
+ %96 = OpLabel
+ OpStore %102 %9
+ OpBranch %103
+ %103 = OpLabel
+ OpLoopMerge %105 %106 None
+ OpBranch %107
+ %107 = OpLabel
+ %108 = OpLoad %6 %102
+ %109 = OpSLessThan %26 %108 %41
+ OpBranchConditional %109 %104 %105
+ %104 = OpLabel
+ OpBranch %106
+ %106 = OpLabel
+ %110 = OpLoad %6 %102
+ %111 = OpIAdd %6 %110 %41
+ OpStore %102 %111
+ OpBranch %103
+ %105 = OpLabel
+ OpBranch %98
+ %98 = OpLabel
+ %112 = OpLoad %6 %94
+ %113 = OpIAdd %6 %112 %41
+ OpStore %94 %113
+ OpBranch %95
+ %97 = OpLabel
+ OpBranch %85
+ %85 = OpLabel
+ OpBranch %82
+ %84 = OpLabel
+ OpStore %81 %9
+ OpBranch %114
+ %114 = OpLabel
+ OpLoopMerge %116 %117 None
+ OpBranch %118
+ %118 = OpLabel
+ %119 = OpLoad %6 %81
+ %120 = OpConvertSToF %16 %119
+ %121 = OpAccessChain %23 %20 %22
+ %122 = OpLoad %16 %121
+ %123 = OpFOrdLessThan %26 %120 %122
+ OpBranchConditional %123 %115 %116
+ %115 = OpLabel
+ %124 = OpLoad %6 %81
+ %125 = OpIAdd %6 %124 %41
+ OpStore %81 %125
+ %126 = OpAccessChain %23 %20 %69
+ %127 = OpLoad %16 %126
+ %128 = OpAccessChain %23 %20 %72
+ %129 = OpLoad %16 %128
+ %130 = OpFOrdEqual %26 %127 %129
+ OpSelectionMerge %132 None
+ OpBranchConditional %130 %131 %132
+ %131 = OpLabel
+ OpBranch %117
+ %132 = OpLabel
+ OpStore %134 %9
+ OpBranch %135
+ %135 = OpLabel
+ OpLoopMerge %137 %138 None
+ OpBranch %139
+ %139 = OpLabel
+ %140 = OpLoad %6 %134
+ %141 = OpSLessThan %26 %140 %41
+ OpBranchConditional %141 %136 %137
+ %136 = OpLabel
+ OpStore %142 %9
+ OpBranch %143
+ %143 = OpLabel
+ OpLoopMerge %145 %146 None
+ OpBranch %147
+ %147 = OpLabel
+ %148 = OpLoad %6 %142
+ %149 = OpSLessThan %26 %148 %41
+ OpBranchConditional %149 %144 %145
+ %144 = OpLabel
+ OpBranch %146
+ %146 = OpLabel
+ %150 = OpLoad %6 %142
+ %151 = OpIAdd %6 %150 %41
+ OpStore %142 %151
+ OpBranch %143
+ %145 = OpLabel
+ %152 = OpAccessChain %23 %20 %69
+ %153 = OpLoad %16 %152
+ %154 = OpAccessChain %23 %20 %72
+ %155 = OpLoad %16 %154
+ %156 = OpFOrdEqual %26 %153 %155
+ OpSelectionMerge %158 None
+ OpBranchConditional %156 %157 %158
+ %157 = OpLabel
+ OpBranch %137
+ %158 = OpLabel
+ OpBranch %138
+ %138 = OpLabel
+ %160 = OpLoad %6 %134
+ %161 = OpIAdd %6 %160 %41
+ OpStore %134 %161
+ OpBranch %135
+ %137 = OpLabel
+ OpBranch %117
+ %117 = OpLabel
+ OpBranch %114
+ %116 = OpLabel
+ OpStore %163 %165
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+ DominatorAnalysis* analysis = context->GetDominatorAnalysis(f);
+
+ EXPECT_TRUE(analysis->Dominates(5, 10));
+ EXPECT_TRUE(analysis->Dominates(5, 46));
+ EXPECT_TRUE(analysis->Dominates(5, 82));
+ EXPECT_TRUE(analysis->Dominates(5, 114));
+ EXPECT_TRUE(analysis->Dominates(5, 116));
+
+ EXPECT_TRUE(analysis->Dominates(10, 14));
+ EXPECT_TRUE(analysis->Dominates(10, 11));
+ EXPECT_TRUE(analysis->Dominates(10, 29));
+ EXPECT_TRUE(analysis->Dominates(10, 33));
+ EXPECT_TRUE(analysis->Dominates(10, 30));
+ EXPECT_TRUE(analysis->Dominates(10, 32));
+ EXPECT_TRUE(analysis->Dominates(10, 31));
+ EXPECT_TRUE(analysis->Dominates(10, 13));
+ EXPECT_TRUE(analysis->Dominates(10, 12));
+
+ EXPECT_TRUE(analysis->Dominates(12, 46));
+
+ EXPECT_TRUE(analysis->Dominates(46, 50));
+ EXPECT_TRUE(analysis->Dominates(46, 47));
+ EXPECT_TRUE(analysis->Dominates(46, 57));
+ EXPECT_TRUE(analysis->Dominates(46, 61));
+ EXPECT_TRUE(analysis->Dominates(46, 58));
+ EXPECT_TRUE(analysis->Dominates(46, 60));
+ EXPECT_TRUE(analysis->Dominates(46, 59));
+ EXPECT_TRUE(analysis->Dominates(46, 77));
+ EXPECT_TRUE(analysis->Dominates(46, 49));
+ EXPECT_TRUE(analysis->Dominates(46, 76));
+ EXPECT_TRUE(analysis->Dominates(46, 48));
+
+ EXPECT_TRUE(analysis->Dominates(48, 82));
+
+ EXPECT_TRUE(analysis->Dominates(82, 86));
+ EXPECT_TRUE(analysis->Dominates(82, 83));
+ EXPECT_TRUE(analysis->Dominates(82, 95));
+ EXPECT_TRUE(analysis->Dominates(82, 99));
+ EXPECT_TRUE(analysis->Dominates(82, 96));
+ EXPECT_TRUE(analysis->Dominates(82, 103));
+ EXPECT_TRUE(analysis->Dominates(82, 107));
+ EXPECT_TRUE(analysis->Dominates(82, 104));
+ EXPECT_TRUE(analysis->Dominates(82, 106));
+ EXPECT_TRUE(analysis->Dominates(82, 105));
+ EXPECT_TRUE(analysis->Dominates(82, 98));
+ EXPECT_TRUE(analysis->Dominates(82, 97));
+ EXPECT_TRUE(analysis->Dominates(82, 85));
+ EXPECT_TRUE(analysis->Dominates(82, 84));
+
+ EXPECT_TRUE(analysis->Dominates(84, 114));
+
+ EXPECT_TRUE(analysis->Dominates(114, 118));
+ EXPECT_TRUE(analysis->Dominates(114, 116));
+ EXPECT_TRUE(analysis->Dominates(114, 115));
+ EXPECT_TRUE(analysis->Dominates(114, 132));
+ EXPECT_TRUE(analysis->Dominates(114, 135));
+ EXPECT_TRUE(analysis->Dominates(114, 139));
+ EXPECT_TRUE(analysis->Dominates(114, 136));
+ EXPECT_TRUE(analysis->Dominates(114, 143));
+ EXPECT_TRUE(analysis->Dominates(114, 147));
+ EXPECT_TRUE(analysis->Dominates(114, 144));
+ EXPECT_TRUE(analysis->Dominates(114, 146));
+ EXPECT_TRUE(analysis->Dominates(114, 145));
+ EXPECT_TRUE(analysis->Dominates(114, 158));
+ EXPECT_TRUE(analysis->Dominates(114, 138));
+ EXPECT_TRUE(analysis->Dominates(114, 137));
+ EXPECT_TRUE(analysis->Dominates(114, 131));
+ EXPECT_TRUE(analysis->Dominates(114, 117));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/nested_loops_with_unreachables.cpp b/test/opt/dominator_tree/nested_loops_with_unreachables.cpp
new file mode 100644
index 0000000..e87e8dd
--- /dev/null
+++ b/test/opt/dominator_tree/nested_loops_with_unreachables.cpp
@@ -0,0 +1,848 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL
+#version 440 core
+layout(location = 0) out vec4 v;
+layout(location = 1) in vec4 in_val;
+void main() {
+ for (int i = 0; i < in_val.x; ++i) {
+ for (int j = 0; j < in_val.y; j++) {
+ }
+ }
+ for (int i = 0; i < in_val.x; ++i) {
+ for (int j = 0; j < in_val.y; j++) {
+ }
+ break;
+ }
+ int i = 0;
+ while (i < in_val.x) {
+ ++i;
+ for (int j = 0; j < 1; j++) {
+ for (int k = 0; k < 1; k++) {
+ }
+ break;
+ }
+ }
+ i = 0;
+ while (i < in_val.x) {
+ ++i;
+ continue;
+ for (int j = 0; j < 1; j++) {
+ for (int k = 0; k < 1; k++) {
+ }
+ break;
+ }
+ }
+ v = vec4(1,1,1,1);
+}
+*/
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %20 %141
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %20 "in_val"
+ OpName %28 "j"
+ OpName %45 "i"
+ OpName %56 "j"
+ OpName %72 "i"
+ OpName %85 "j"
+ OpName %93 "k"
+ OpName %119 "j"
+ OpName %127 "k"
+ OpName %141 "v"
+ OpDecorate %20 Location 1
+ OpDecorate %141 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpTypeFloat 32
+ %18 = OpTypeVector %16 4
+ %19 = OpTypePointer Input %18
+ %20 = OpVariable %19 Input
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 0
+ %23 = OpTypePointer Input %16
+ %26 = OpTypeBool
+ %36 = OpConstant %21 1
+ %41 = OpConstant %6 1
+ %140 = OpTypePointer Output %18
+ %141 = OpVariable %140 Output
+ %142 = OpConstant %16 1
+ %143 = OpConstantComposite %18 %142 %142 %142 %142
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %28 = OpVariable %7 Function
+ %45 = OpVariable %7 Function
+ %56 = OpVariable %7 Function
+ %72 = OpVariable %7 Function
+ %85 = OpVariable %7 Function
+ %93 = OpVariable %7 Function
+ %119 = OpVariable %7 Function
+ %127 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %17 = OpConvertSToF %16 %15
+ %24 = OpAccessChain %23 %20 %22
+ %25 = OpLoad %16 %24
+ %27 = OpFOrdLessThan %26 %17 %25
+ OpBranchConditional %27 %11 %12
+ %11 = OpLabel
+ OpStore %28 %9
+ OpBranch %29
+ %29 = OpLabel
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+ %34 = OpLoad %6 %28
+ %35 = OpConvertSToF %16 %34
+ %37 = OpAccessChain %23 %20 %36
+ %38 = OpLoad %16 %37
+ %39 = OpFOrdLessThan %26 %35 %38
+ OpBranchConditional %39 %30 %31
+ %30 = OpLabel
+ OpBranch %32
+ %32 = OpLabel
+ %40 = OpLoad %6 %28
+ %42 = OpIAdd %6 %40 %41
+ OpStore %28 %42
+ OpBranch %29
+ %31 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %43 = OpLoad %6 %8
+ %44 = OpIAdd %6 %43 %41
+ OpStore %8 %44
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %45 %9
+ OpBranch %46
+ %46 = OpLabel
+ OpLoopMerge %48 %49 None
+ OpBranch %50
+ %50 = OpLabel
+ %51 = OpLoad %6 %45
+ %52 = OpConvertSToF %16 %51
+ %53 = OpAccessChain %23 %20 %22
+ %54 = OpLoad %16 %53
+ %55 = OpFOrdLessThan %26 %52 %54
+ OpBranchConditional %55 %47 %48
+ %47 = OpLabel
+ OpStore %56 %9
+ OpBranch %57
+ %57 = OpLabel
+ OpLoopMerge %59 %60 None
+ OpBranch %61
+ %61 = OpLabel
+ %62 = OpLoad %6 %56
+ %63 = OpConvertSToF %16 %62
+ %64 = OpAccessChain %23 %20 %36
+ %65 = OpLoad %16 %64
+ %66 = OpFOrdLessThan %26 %63 %65
+ OpBranchConditional %66 %58 %59
+ %58 = OpLabel
+ OpBranch %60
+ %60 = OpLabel
+ %67 = OpLoad %6 %56
+ %68 = OpIAdd %6 %67 %41
+ OpStore %56 %68
+ OpBranch %57
+ %59 = OpLabel
+ OpBranch %48
+ %49 = OpLabel
+ %70 = OpLoad %6 %45
+ %71 = OpIAdd %6 %70 %41
+ OpStore %45 %71
+ OpBranch %46
+ %48 = OpLabel
+ OpStore %72 %9
+ OpBranch %73
+ %73 = OpLabel
+ OpLoopMerge %75 %76 None
+ OpBranch %77
+ %77 = OpLabel
+ %78 = OpLoad %6 %72
+ %79 = OpConvertSToF %16 %78
+ %80 = OpAccessChain %23 %20 %22
+ %81 = OpLoad %16 %80
+ %82 = OpFOrdLessThan %26 %79 %81
+ OpBranchConditional %82 %74 %75
+ %74 = OpLabel
+ %83 = OpLoad %6 %72
+ %84 = OpIAdd %6 %83 %41
+ OpStore %72 %84
+ OpStore %85 %9
+ OpBranch %86
+ %86 = OpLabel
+ OpLoopMerge %88 %89 None
+ OpBranch %90
+ %90 = OpLabel
+ %91 = OpLoad %6 %85
+ %92 = OpSLessThan %26 %91 %41
+ OpBranchConditional %92 %87 %88
+ %87 = OpLabel
+ OpStore %93 %9
+ OpBranch %94
+ %94 = OpLabel
+ OpLoopMerge %96 %97 None
+ OpBranch %98
+ %98 = OpLabel
+ %99 = OpLoad %6 %93
+ %100 = OpSLessThan %26 %99 %41
+ OpBranchConditional %100 %95 %96
+ %95 = OpLabel
+ OpBranch %97
+ %97 = OpLabel
+ %101 = OpLoad %6 %93
+ %102 = OpIAdd %6 %101 %41
+ OpStore %93 %102
+ OpBranch %94
+ %96 = OpLabel
+ OpBranch %88
+ %89 = OpLabel
+ %104 = OpLoad %6 %85
+ %105 = OpIAdd %6 %104 %41
+ OpStore %85 %105
+ OpBranch %86
+ %88 = OpLabel
+ OpBranch %76
+ %76 = OpLabel
+ OpBranch %73
+ %75 = OpLabel
+ OpStore %72 %9
+ OpBranch %106
+ %106 = OpLabel
+ OpLoopMerge %108 %109 None
+ OpBranch %110
+ %110 = OpLabel
+ %111 = OpLoad %6 %72
+ %112 = OpConvertSToF %16 %111
+ %113 = OpAccessChain %23 %20 %22
+ %114 = OpLoad %16 %113
+ %115 = OpFOrdLessThan %26 %112 %114
+ OpBranchConditional %115 %107 %108
+ %107 = OpLabel
+ %116 = OpLoad %6 %72
+ %117 = OpIAdd %6 %116 %41
+ OpStore %72 %117
+ OpBranch %109
+ %109 = OpLabel
+ OpBranch %106
+ %108 = OpLabel
+ OpStore %141 %143
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+ DominatorAnalysis* analysis = context->GetDominatorAnalysis(f);
+
+ EXPECT_TRUE(analysis->Dominates(5, 10));
+ EXPECT_TRUE(analysis->Dominates(5, 14));
+ EXPECT_TRUE(analysis->Dominates(5, 11));
+ EXPECT_TRUE(analysis->Dominates(5, 29));
+ EXPECT_TRUE(analysis->Dominates(5, 33));
+ EXPECT_TRUE(analysis->Dominates(5, 30));
+ EXPECT_TRUE(analysis->Dominates(5, 32));
+ EXPECT_TRUE(analysis->Dominates(5, 31));
+ EXPECT_TRUE(analysis->Dominates(5, 13));
+ EXPECT_TRUE(analysis->Dominates(5, 12));
+ EXPECT_TRUE(analysis->Dominates(5, 46));
+ EXPECT_TRUE(analysis->Dominates(5, 50));
+ EXPECT_TRUE(analysis->Dominates(5, 47));
+ EXPECT_TRUE(analysis->Dominates(5, 57));
+ EXPECT_TRUE(analysis->Dominates(5, 61));
+ EXPECT_TRUE(analysis->Dominates(5, 59));
+ EXPECT_TRUE(analysis->Dominates(5, 58));
+ EXPECT_TRUE(analysis->Dominates(5, 60));
+ EXPECT_TRUE(analysis->Dominates(5, 48));
+ EXPECT_TRUE(analysis->Dominates(5, 73));
+ EXPECT_TRUE(analysis->Dominates(5, 77));
+ EXPECT_TRUE(analysis->Dominates(5, 75));
+ EXPECT_TRUE(analysis->Dominates(5, 106));
+ EXPECT_TRUE(analysis->Dominates(5, 110));
+ EXPECT_TRUE(analysis->Dominates(5, 107));
+ EXPECT_TRUE(analysis->Dominates(5, 108));
+ EXPECT_TRUE(analysis->Dominates(5, 109));
+ EXPECT_TRUE(analysis->Dominates(5, 74));
+ EXPECT_TRUE(analysis->Dominates(5, 86));
+ EXPECT_TRUE(analysis->Dominates(5, 90));
+ EXPECT_TRUE(analysis->Dominates(5, 87));
+ EXPECT_TRUE(analysis->Dominates(5, 94));
+ EXPECT_TRUE(analysis->Dominates(5, 98));
+ EXPECT_TRUE(analysis->Dominates(5, 95));
+ EXPECT_TRUE(analysis->Dominates(5, 97));
+ EXPECT_TRUE(analysis->Dominates(5, 96));
+ EXPECT_TRUE(analysis->Dominates(5, 88));
+ EXPECT_TRUE(analysis->Dominates(5, 76));
+
+ EXPECT_TRUE(analysis->Dominates(10, 14));
+ EXPECT_TRUE(analysis->Dominates(10, 11));
+ EXPECT_TRUE(analysis->Dominates(10, 29));
+ EXPECT_TRUE(analysis->Dominates(10, 33));
+ EXPECT_TRUE(analysis->Dominates(10, 30));
+ EXPECT_TRUE(analysis->Dominates(10, 32));
+ EXPECT_TRUE(analysis->Dominates(10, 31));
+ EXPECT_TRUE(analysis->Dominates(10, 13));
+ EXPECT_TRUE(analysis->Dominates(10, 12));
+ EXPECT_TRUE(analysis->Dominates(10, 46));
+ EXPECT_TRUE(analysis->Dominates(10, 50));
+ EXPECT_TRUE(analysis->Dominates(10, 47));
+ EXPECT_TRUE(analysis->Dominates(10, 57));
+ EXPECT_TRUE(analysis->Dominates(10, 61));
+ EXPECT_TRUE(analysis->Dominates(10, 59));
+ EXPECT_TRUE(analysis->Dominates(10, 58));
+ EXPECT_TRUE(analysis->Dominates(10, 60));
+ EXPECT_TRUE(analysis->Dominates(10, 48));
+ EXPECT_TRUE(analysis->Dominates(10, 73));
+ EXPECT_TRUE(analysis->Dominates(10, 77));
+ EXPECT_TRUE(analysis->Dominates(10, 75));
+ EXPECT_TRUE(analysis->Dominates(10, 106));
+ EXPECT_TRUE(analysis->Dominates(10, 110));
+ EXPECT_TRUE(analysis->Dominates(10, 107));
+ EXPECT_TRUE(analysis->Dominates(10, 108));
+ EXPECT_TRUE(analysis->Dominates(10, 109));
+ EXPECT_TRUE(analysis->Dominates(10, 74));
+ EXPECT_TRUE(analysis->Dominates(10, 86));
+ EXPECT_TRUE(analysis->Dominates(10, 90));
+ EXPECT_TRUE(analysis->Dominates(10, 87));
+ EXPECT_TRUE(analysis->Dominates(10, 94));
+ EXPECT_TRUE(analysis->Dominates(10, 98));
+ EXPECT_TRUE(analysis->Dominates(10, 95));
+ EXPECT_TRUE(analysis->Dominates(10, 97));
+ EXPECT_TRUE(analysis->Dominates(10, 96));
+ EXPECT_TRUE(analysis->Dominates(10, 88));
+ EXPECT_TRUE(analysis->Dominates(10, 76));
+
+ EXPECT_TRUE(analysis->Dominates(14, 11));
+ EXPECT_TRUE(analysis->Dominates(14, 29));
+ EXPECT_TRUE(analysis->Dominates(14, 33));
+ EXPECT_TRUE(analysis->Dominates(14, 30));
+ EXPECT_TRUE(analysis->Dominates(14, 32));
+ EXPECT_TRUE(analysis->Dominates(14, 31));
+
+ EXPECT_TRUE(analysis->Dominates(11, 29));
+ EXPECT_TRUE(analysis->Dominates(11, 33));
+ EXPECT_TRUE(analysis->Dominates(11, 30));
+ EXPECT_TRUE(analysis->Dominates(11, 32));
+ EXPECT_TRUE(analysis->Dominates(11, 31));
+
+ EXPECT_TRUE(analysis->Dominates(29, 33));
+ EXPECT_TRUE(analysis->Dominates(29, 30));
+ EXPECT_TRUE(analysis->Dominates(29, 32));
+ EXPECT_TRUE(analysis->Dominates(29, 31));
+
+ EXPECT_TRUE(analysis->Dominates(33, 30));
+
+ EXPECT_TRUE(analysis->Dominates(12, 46));
+ EXPECT_TRUE(analysis->Dominates(12, 50));
+ EXPECT_TRUE(analysis->Dominates(12, 47));
+ EXPECT_TRUE(analysis->Dominates(12, 57));
+ EXPECT_TRUE(analysis->Dominates(12, 61));
+ EXPECT_TRUE(analysis->Dominates(12, 59));
+ EXPECT_TRUE(analysis->Dominates(12, 58));
+ EXPECT_TRUE(analysis->Dominates(12, 60));
+ EXPECT_TRUE(analysis->Dominates(12, 48));
+ EXPECT_TRUE(analysis->Dominates(12, 73));
+ EXPECT_TRUE(analysis->Dominates(12, 77));
+ EXPECT_TRUE(analysis->Dominates(12, 75));
+ EXPECT_TRUE(analysis->Dominates(12, 106));
+ EXPECT_TRUE(analysis->Dominates(12, 110));
+ EXPECT_TRUE(analysis->Dominates(12, 107));
+ EXPECT_TRUE(analysis->Dominates(12, 108));
+ EXPECT_TRUE(analysis->Dominates(12, 109));
+ EXPECT_TRUE(analysis->Dominates(12, 74));
+ EXPECT_TRUE(analysis->Dominates(12, 86));
+ EXPECT_TRUE(analysis->Dominates(12, 90));
+ EXPECT_TRUE(analysis->Dominates(12, 87));
+ EXPECT_TRUE(analysis->Dominates(12, 94));
+ EXPECT_TRUE(analysis->Dominates(12, 98));
+ EXPECT_TRUE(analysis->Dominates(12, 95));
+ EXPECT_TRUE(analysis->Dominates(12, 97));
+ EXPECT_TRUE(analysis->Dominates(12, 96));
+ EXPECT_TRUE(analysis->Dominates(12, 88));
+ EXPECT_TRUE(analysis->Dominates(12, 76));
+
+ EXPECT_TRUE(analysis->Dominates(46, 50));
+ EXPECT_TRUE(analysis->Dominates(46, 47));
+ EXPECT_TRUE(analysis->Dominates(46, 57));
+ EXPECT_TRUE(analysis->Dominates(46, 61));
+ EXPECT_TRUE(analysis->Dominates(46, 59));
+ EXPECT_TRUE(analysis->Dominates(46, 58));
+ EXPECT_TRUE(analysis->Dominates(46, 60));
+ EXPECT_TRUE(analysis->Dominates(46, 48));
+ EXPECT_TRUE(analysis->Dominates(46, 73));
+ EXPECT_TRUE(analysis->Dominates(46, 77));
+ EXPECT_TRUE(analysis->Dominates(46, 75));
+ EXPECT_TRUE(analysis->Dominates(46, 106));
+ EXPECT_TRUE(analysis->Dominates(46, 110));
+ EXPECT_TRUE(analysis->Dominates(46, 107));
+ EXPECT_TRUE(analysis->Dominates(46, 108));
+ EXPECT_TRUE(analysis->Dominates(46, 109));
+ EXPECT_TRUE(analysis->Dominates(46, 74));
+ EXPECT_TRUE(analysis->Dominates(46, 86));
+ EXPECT_TRUE(analysis->Dominates(46, 90));
+ EXPECT_TRUE(analysis->Dominates(46, 87));
+ EXPECT_TRUE(analysis->Dominates(46, 94));
+ EXPECT_TRUE(analysis->Dominates(46, 98));
+ EXPECT_TRUE(analysis->Dominates(46, 95));
+ EXPECT_TRUE(analysis->Dominates(46, 97));
+ EXPECT_TRUE(analysis->Dominates(46, 96));
+ EXPECT_TRUE(analysis->Dominates(46, 88));
+ EXPECT_TRUE(analysis->Dominates(46, 76));
+
+ EXPECT_TRUE(analysis->Dominates(50, 47));
+ EXPECT_TRUE(analysis->Dominates(50, 57));
+ EXPECT_TRUE(analysis->Dominates(50, 61));
+ EXPECT_TRUE(analysis->Dominates(50, 59));
+ EXPECT_TRUE(analysis->Dominates(50, 58));
+ EXPECT_TRUE(analysis->Dominates(50, 60));
+
+ EXPECT_TRUE(analysis->Dominates(47, 57));
+ EXPECT_TRUE(analysis->Dominates(47, 61));
+ EXPECT_TRUE(analysis->Dominates(47, 59));
+ EXPECT_TRUE(analysis->Dominates(47, 58));
+ EXPECT_TRUE(analysis->Dominates(47, 60));
+
+ EXPECT_TRUE(analysis->Dominates(57, 61));
+ EXPECT_TRUE(analysis->Dominates(57, 59));
+ EXPECT_TRUE(analysis->Dominates(57, 58));
+ EXPECT_TRUE(analysis->Dominates(57, 60));
+
+ EXPECT_TRUE(analysis->Dominates(61, 59));
+
+ EXPECT_TRUE(analysis->Dominates(48, 73));
+ EXPECT_TRUE(analysis->Dominates(48, 77));
+ EXPECT_TRUE(analysis->Dominates(48, 75));
+ EXPECT_TRUE(analysis->Dominates(48, 106));
+ EXPECT_TRUE(analysis->Dominates(48, 110));
+ EXPECT_TRUE(analysis->Dominates(48, 107));
+ EXPECT_TRUE(analysis->Dominates(48, 108));
+ EXPECT_TRUE(analysis->Dominates(48, 109));
+ EXPECT_TRUE(analysis->Dominates(48, 74));
+ EXPECT_TRUE(analysis->Dominates(48, 86));
+ EXPECT_TRUE(analysis->Dominates(48, 90));
+ EXPECT_TRUE(analysis->Dominates(48, 87));
+ EXPECT_TRUE(analysis->Dominates(48, 94));
+ EXPECT_TRUE(analysis->Dominates(48, 98));
+ EXPECT_TRUE(analysis->Dominates(48, 95));
+ EXPECT_TRUE(analysis->Dominates(48, 97));
+ EXPECT_TRUE(analysis->Dominates(48, 96));
+ EXPECT_TRUE(analysis->Dominates(48, 88));
+ EXPECT_TRUE(analysis->Dominates(48, 76));
+
+ EXPECT_TRUE(analysis->Dominates(73, 77));
+ EXPECT_TRUE(analysis->Dominates(73, 75));
+ EXPECT_TRUE(analysis->Dominates(73, 106));
+ EXPECT_TRUE(analysis->Dominates(73, 110));
+ EXPECT_TRUE(analysis->Dominates(73, 107));
+ EXPECT_TRUE(analysis->Dominates(73, 108));
+ EXPECT_TRUE(analysis->Dominates(73, 109));
+ EXPECT_TRUE(analysis->Dominates(73, 74));
+ EXPECT_TRUE(analysis->Dominates(73, 86));
+ EXPECT_TRUE(analysis->Dominates(73, 90));
+ EXPECT_TRUE(analysis->Dominates(73, 87));
+ EXPECT_TRUE(analysis->Dominates(73, 94));
+ EXPECT_TRUE(analysis->Dominates(73, 98));
+ EXPECT_TRUE(analysis->Dominates(73, 95));
+ EXPECT_TRUE(analysis->Dominates(73, 97));
+ EXPECT_TRUE(analysis->Dominates(73, 96));
+ EXPECT_TRUE(analysis->Dominates(73, 88));
+ EXPECT_TRUE(analysis->Dominates(73, 76));
+
+ EXPECT_TRUE(analysis->Dominates(75, 106));
+ EXPECT_TRUE(analysis->Dominates(75, 110));
+ EXPECT_TRUE(analysis->Dominates(75, 107));
+ EXPECT_TRUE(analysis->Dominates(75, 108));
+ EXPECT_TRUE(analysis->Dominates(75, 109));
+
+ EXPECT_TRUE(analysis->Dominates(106, 110));
+ EXPECT_TRUE(analysis->Dominates(106, 107));
+ EXPECT_TRUE(analysis->Dominates(106, 108));
+ EXPECT_TRUE(analysis->Dominates(106, 109));
+
+ EXPECT_TRUE(analysis->Dominates(110, 107));
+
+ EXPECT_TRUE(analysis->Dominates(77, 74));
+ EXPECT_TRUE(analysis->Dominates(77, 86));
+ EXPECT_TRUE(analysis->Dominates(77, 90));
+ EXPECT_TRUE(analysis->Dominates(77, 87));
+ EXPECT_TRUE(analysis->Dominates(77, 94));
+ EXPECT_TRUE(analysis->Dominates(77, 98));
+ EXPECT_TRUE(analysis->Dominates(77, 95));
+ EXPECT_TRUE(analysis->Dominates(77, 97));
+ EXPECT_TRUE(analysis->Dominates(77, 96));
+ EXPECT_TRUE(analysis->Dominates(77, 88));
+
+ EXPECT_TRUE(analysis->Dominates(74, 86));
+ EXPECT_TRUE(analysis->Dominates(74, 90));
+ EXPECT_TRUE(analysis->Dominates(74, 87));
+ EXPECT_TRUE(analysis->Dominates(74, 94));
+ EXPECT_TRUE(analysis->Dominates(74, 98));
+ EXPECT_TRUE(analysis->Dominates(74, 95));
+ EXPECT_TRUE(analysis->Dominates(74, 97));
+ EXPECT_TRUE(analysis->Dominates(74, 96));
+ EXPECT_TRUE(analysis->Dominates(74, 88));
+
+ EXPECT_TRUE(analysis->Dominates(86, 90));
+ EXPECT_TRUE(analysis->Dominates(86, 87));
+ EXPECT_TRUE(analysis->Dominates(86, 94));
+ EXPECT_TRUE(analysis->Dominates(86, 98));
+ EXPECT_TRUE(analysis->Dominates(86, 95));
+ EXPECT_TRUE(analysis->Dominates(86, 97));
+ EXPECT_TRUE(analysis->Dominates(86, 96));
+ EXPECT_TRUE(analysis->Dominates(86, 88));
+
+ EXPECT_TRUE(analysis->Dominates(90, 87));
+ EXPECT_TRUE(analysis->Dominates(90, 94));
+ EXPECT_TRUE(analysis->Dominates(90, 98));
+ EXPECT_TRUE(analysis->Dominates(90, 95));
+ EXPECT_TRUE(analysis->Dominates(90, 97));
+ EXPECT_TRUE(analysis->Dominates(90, 96));
+
+ EXPECT_TRUE(analysis->Dominates(87, 94));
+ EXPECT_TRUE(analysis->Dominates(87, 98));
+ EXPECT_TRUE(analysis->Dominates(87, 95));
+ EXPECT_TRUE(analysis->Dominates(87, 97));
+ EXPECT_TRUE(analysis->Dominates(87, 96));
+
+ EXPECT_TRUE(analysis->Dominates(94, 98));
+ EXPECT_TRUE(analysis->Dominates(94, 95));
+ EXPECT_TRUE(analysis->Dominates(94, 97));
+ EXPECT_TRUE(analysis->Dominates(94, 96));
+
+ EXPECT_TRUE(analysis->Dominates(98, 95));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 10));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 14));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 29));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 33));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 30));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 32));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 31));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 13));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 12));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 46));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 50));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 47));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 57));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 61));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 59));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 58));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 60));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 48));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 73));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 77));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 75));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 106));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 109));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 74));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 88));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 76));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 14));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 29));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 33));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 30));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 32));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 31));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 13));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 12));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 46));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 50));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 47));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 57));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 61));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 59));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 58));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 60));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 48));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 73));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 77));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 75));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 106));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 109));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 74));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 88));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 76));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 29));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 33));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 30));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 32));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 31));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 29));
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 33));
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 30));
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 32));
+ EXPECT_TRUE(analysis->StrictlyDominates(11, 31));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(29, 33));
+ EXPECT_TRUE(analysis->StrictlyDominates(29, 30));
+ EXPECT_TRUE(analysis->StrictlyDominates(29, 32));
+ EXPECT_TRUE(analysis->StrictlyDominates(29, 31));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(33, 30));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 46));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 50));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 47));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 57));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 61));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 59));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 58));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 60));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 48));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 73));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 77));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 75));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 106));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 109));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 74));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 88));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 76));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 50));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 47));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 57));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 61));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 59));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 58));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 60));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 48));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 73));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 77));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 75));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 106));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 109));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 74));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 88));
+ EXPECT_TRUE(analysis->StrictlyDominates(46, 76));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(50, 47));
+ EXPECT_TRUE(analysis->StrictlyDominates(50, 57));
+ EXPECT_TRUE(analysis->StrictlyDominates(50, 61));
+ EXPECT_TRUE(analysis->StrictlyDominates(50, 59));
+ EXPECT_TRUE(analysis->StrictlyDominates(50, 58));
+ EXPECT_TRUE(analysis->StrictlyDominates(50, 60));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(47, 57));
+ EXPECT_TRUE(analysis->StrictlyDominates(47, 61));
+ EXPECT_TRUE(analysis->StrictlyDominates(47, 59));
+ EXPECT_TRUE(analysis->StrictlyDominates(47, 58));
+ EXPECT_TRUE(analysis->StrictlyDominates(47, 60));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(57, 61));
+ EXPECT_TRUE(analysis->StrictlyDominates(57, 59));
+ EXPECT_TRUE(analysis->StrictlyDominates(57, 58));
+ EXPECT_TRUE(analysis->StrictlyDominates(57, 60));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(61, 59));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 73));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 77));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 75));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 106));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 109));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 74));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 88));
+ EXPECT_TRUE(analysis->StrictlyDominates(48, 76));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 77));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 75));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 106));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 109));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 74));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 88));
+ EXPECT_TRUE(analysis->StrictlyDominates(73, 76));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(75, 106));
+ EXPECT_TRUE(analysis->StrictlyDominates(75, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(75, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(75, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(75, 109));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(106, 110));
+ EXPECT_TRUE(analysis->StrictlyDominates(106, 107));
+ EXPECT_TRUE(analysis->StrictlyDominates(106, 108));
+ EXPECT_TRUE(analysis->StrictlyDominates(106, 109));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(110, 107));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 74));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(77, 88));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 86));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(74, 88));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 90));
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 96));
+ EXPECT_TRUE(analysis->StrictlyDominates(86, 88));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(90, 87));
+ EXPECT_TRUE(analysis->StrictlyDominates(90, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(90, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(90, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(90, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(90, 96));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(87, 94));
+ EXPECT_TRUE(analysis->StrictlyDominates(87, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(87, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(87, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(87, 96));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(94, 98));
+ EXPECT_TRUE(analysis->StrictlyDominates(94, 95));
+ EXPECT_TRUE(analysis->StrictlyDominates(94, 97));
+ EXPECT_TRUE(analysis->StrictlyDominates(94, 96));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(98, 95));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/pch_test_opt_dom.cpp b/test/opt/dominator_tree/pch_test_opt_dom.cpp
new file mode 100644
index 0000000..a28310e
--- /dev/null
+++ b/test/opt/dominator_tree/pch_test_opt_dom.cpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2018 The Khronos Group 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 "pch_test_opt_dom.h"
diff --git a/test/opt/dominator_tree/pch_test_opt_dom.h b/test/opt/dominator_tree/pch_test_opt_dom.h
new file mode 100644
index 0000000..4e8106f
--- /dev/null
+++ b/test/opt/dominator_tree/pch_test_opt_dom.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2018 The Khronos Group 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 "gmock/gmock.h"
+#include "source/opt/iterator.h"
+#include "source/opt/loop_dependence.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "source/opt/scalar_analysis.h"
+#include "source/opt/tree_iterator.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
diff --git a/test/opt/dominator_tree/post.cpp b/test/opt/dominator_tree/post.cpp
new file mode 100644
index 0000000..bb10fde
--- /dev/null
+++ b/test/opt/dominator_tree/post.cpp
@@ -0,0 +1,207 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Generated from the following GLSL
+#version 440 core
+layout(location = 0) out vec4 c;
+layout(location = 1)in vec4 in_val;
+void main(){
+ if ( in_val.x < 10) {
+ int z = 0;
+ int i = 0;
+ for (i = 0; i < in_val.y; ++i) {
+ z += i;
+ }
+ c = vec4(i,i,i,i);
+ } else {
+ c = vec4(1,1,1,1);
+ }
+}
+*/
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %9 %43
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %9 "in_val"
+ OpName %22 "z"
+ OpName %24 "i"
+ OpName %43 "c"
+ OpDecorate %9 Location 1
+ OpDecorate %43 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Input %7
+ %9 = OpVariable %8 Input
+ %10 = OpTypeInt 32 0
+ %11 = OpConstant %10 0
+ %12 = OpTypePointer Input %6
+ %15 = OpConstant %6 10
+ %16 = OpTypeBool
+ %20 = OpTypeInt 32 1
+ %21 = OpTypePointer Function %20
+ %23 = OpConstant %20 0
+ %32 = OpConstant %10 1
+ %40 = OpConstant %20 1
+ %42 = OpTypePointer Output %7
+ %43 = OpVariable %42 Output
+ %54 = OpConstant %6 1
+ %55 = OpConstantComposite %7 %54 %54 %54 %54
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %22 = OpVariable %21 Function
+ %24 = OpVariable %21 Function
+ %13 = OpAccessChain %12 %9 %11
+ %14 = OpLoad %6 %13
+ %17 = OpFOrdLessThan %16 %14 %15
+ OpSelectionMerge %19 None
+ OpBranchConditional %17 %18 %53
+ %18 = OpLabel
+ OpStore %22 %23
+ OpStore %24 %23
+ OpStore %24 %23
+ OpBranch %25
+ %25 = OpLabel
+ OpLoopMerge %27 %28 None
+ OpBranch %29
+ %29 = OpLabel
+ %30 = OpLoad %20 %24
+ %31 = OpConvertSToF %6 %30
+ %33 = OpAccessChain %12 %9 %32
+ %34 = OpLoad %6 %33
+ %35 = OpFOrdLessThan %16 %31 %34
+ OpBranchConditional %35 %26 %27
+ %26 = OpLabel
+ %36 = OpLoad %20 %24
+ %37 = OpLoad %20 %22
+ %38 = OpIAdd %20 %37 %36
+ OpStore %22 %38
+ OpBranch %28
+ %28 = OpLabel
+ %39 = OpLoad %20 %24
+ %41 = OpIAdd %20 %39 %40
+ OpStore %24 %41
+ OpBranch %25
+ %27 = OpLabel
+ %44 = OpLoad %20 %24
+ %45 = OpConvertSToF %6 %44
+ %46 = OpLoad %20 %24
+ %47 = OpConvertSToF %6 %46
+ %48 = OpLoad %20 %24
+ %49 = OpConvertSToF %6 %48
+ %50 = OpLoad %20 %24
+ %51 = OpConvertSToF %6 %50
+ %52 = OpCompositeConstruct %7 %45 %47 %49 %51
+ OpStore %43 %52
+ OpBranch %19
+ %53 = OpLabel
+ OpStore %43 %55
+ OpBranch %19
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+ CFG cfg(module);
+ PostDominatorAnalysis* analysis = context->GetPostDominatorAnalysis(f);
+
+ EXPECT_TRUE(analysis->Dominates(19, 18));
+ EXPECT_TRUE(analysis->Dominates(19, 5));
+ EXPECT_TRUE(analysis->Dominates(19, 53));
+ EXPECT_TRUE(analysis->Dominates(19, 19));
+ EXPECT_TRUE(analysis->Dominates(19, 25));
+ EXPECT_TRUE(analysis->Dominates(19, 29));
+ EXPECT_TRUE(analysis->Dominates(19, 27));
+ EXPECT_TRUE(analysis->Dominates(19, 26));
+ EXPECT_TRUE(analysis->Dominates(19, 28));
+
+ EXPECT_TRUE(analysis->Dominates(27, 18));
+ EXPECT_TRUE(analysis->Dominates(27, 25));
+ EXPECT_TRUE(analysis->Dominates(27, 29));
+ EXPECT_TRUE(analysis->Dominates(27, 27));
+ EXPECT_TRUE(analysis->Dominates(27, 26));
+ EXPECT_TRUE(analysis->Dominates(27, 28));
+
+ EXPECT_FALSE(analysis->Dominates(27, 19));
+ EXPECT_FALSE(analysis->Dominates(27, 5));
+ EXPECT_FALSE(analysis->Dominates(27, 53));
+
+ EXPECT_FALSE(analysis->StrictlyDominates(19, 19));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 18));
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 5));
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 53));
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 25));
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 29));
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 27));
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 26));
+ EXPECT_TRUE(analysis->StrictlyDominates(19, 28));
+
+ // These would be expected true for a normal, non post, dominator tree
+ EXPECT_FALSE(analysis->Dominates(5, 18));
+ EXPECT_FALSE(analysis->Dominates(5, 53));
+ EXPECT_FALSE(analysis->Dominates(5, 19));
+ EXPECT_FALSE(analysis->Dominates(5, 25));
+ EXPECT_FALSE(analysis->Dominates(5, 29));
+ EXPECT_FALSE(analysis->Dominates(5, 27));
+ EXPECT_FALSE(analysis->Dominates(5, 26));
+ EXPECT_FALSE(analysis->Dominates(5, 28));
+
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 18));
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 53));
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 19));
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 25));
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 29));
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 27));
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 26));
+ EXPECT_FALSE(analysis->StrictlyDominates(5, 28));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/simple.cpp b/test/opt/dominator_tree/simple.cpp
new file mode 100644
index 0000000..d11854d
--- /dev/null
+++ b/test/opt/dominator_tree/simple.cpp
@@ -0,0 +1,177 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL
+#version 440 core
+layout(location = 0) out vec4 c;
+layout(location = 1)in vec4 in_val;
+void main(){
+ if ( in_val.x < 10) {
+ int z = 0;
+ int i = 0;
+ for (i = 0; i < in_val.y; ++i) {
+ z += i;
+ }
+ c = vec4(i,i,i,i);
+ } else {
+ c = vec4(1,1,1,1);
+ }
+}
+*/
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %9 %43
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %9 "in_val"
+ OpName %22 "z"
+ OpName %24 "i"
+ OpName %43 "c"
+ OpDecorate %9 Location 1
+ OpDecorate %43 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Input %7
+ %9 = OpVariable %8 Input
+ %10 = OpTypeInt 32 0
+ %11 = OpConstant %10 0
+ %12 = OpTypePointer Input %6
+ %15 = OpConstant %6 10
+ %16 = OpTypeBool
+ %20 = OpTypeInt 32 1
+ %21 = OpTypePointer Function %20
+ %23 = OpConstant %20 0
+ %32 = OpConstant %10 1
+ %40 = OpConstant %20 1
+ %42 = OpTypePointer Output %7
+ %43 = OpVariable %42 Output
+ %54 = OpConstant %6 1
+ %55 = OpConstantComposite %7 %54 %54 %54 %54
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %22 = OpVariable %21 Function
+ %24 = OpVariable %21 Function
+ %13 = OpAccessChain %12 %9 %11
+ %14 = OpLoad %6 %13
+ %17 = OpFOrdLessThan %16 %14 %15
+ OpSelectionMerge %19 None
+ OpBranchConditional %17 %18 %53
+ %18 = OpLabel
+ OpStore %22 %23
+ OpStore %24 %23
+ OpStore %24 %23
+ OpBranch %25
+ %25 = OpLabel
+ OpLoopMerge %27 %28 None
+ OpBranch %29
+ %29 = OpLabel
+ %30 = OpLoad %20 %24
+ %31 = OpConvertSToF %6 %30
+ %33 = OpAccessChain %12 %9 %32
+ %34 = OpLoad %6 %33
+ %35 = OpFOrdLessThan %16 %31 %34
+ OpBranchConditional %35 %26 %27
+ %26 = OpLabel
+ %36 = OpLoad %20 %24
+ %37 = OpLoad %20 %22
+ %38 = OpIAdd %20 %37 %36
+ OpStore %22 %38
+ OpBranch %28
+ %28 = OpLabel
+ %39 = OpLoad %20 %24
+ %41 = OpIAdd %20 %39 %40
+ OpStore %24 %41
+ OpBranch %25
+ %27 = OpLabel
+ %44 = OpLoad %20 %24
+ %45 = OpConvertSToF %6 %44
+ %46 = OpLoad %20 %24
+ %47 = OpConvertSToF %6 %46
+ %48 = OpLoad %20 %24
+ %49 = OpConvertSToF %6 %48
+ %50 = OpLoad %20 %24
+ %51 = OpConvertSToF %6 %50
+ %52 = OpCompositeConstruct %7 %45 %47 %49 %51
+ OpStore %43 %52
+ OpBranch %19
+ %53 = OpLabel
+ OpStore %43 %55
+ OpBranch %19
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+
+ DominatorAnalysis* analysis = context->GetDominatorAnalysis(f);
+ const CFG& cfg = *context->cfg();
+
+ DominatorTree& tree = analysis->GetDomTree();
+
+ EXPECT_EQ(tree.GetRoot()->bb_, cfg.pseudo_entry_block());
+ EXPECT_TRUE(analysis->Dominates(5, 18));
+ EXPECT_TRUE(analysis->Dominates(5, 53));
+ EXPECT_TRUE(analysis->Dominates(5, 19));
+ EXPECT_TRUE(analysis->Dominates(5, 25));
+ EXPECT_TRUE(analysis->Dominates(5, 29));
+ EXPECT_TRUE(analysis->Dominates(5, 27));
+ EXPECT_TRUE(analysis->Dominates(5, 26));
+ EXPECT_TRUE(analysis->Dominates(5, 28));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 18));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 53));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 19));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 25));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 29));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 27));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 26));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 28));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/switch_case_fallthrough.cpp b/test/opt/dominator_tree/switch_case_fallthrough.cpp
new file mode 100644
index 0000000..d9dd7d1
--- /dev/null
+++ b/test/opt/dominator_tree/switch_case_fallthrough.cpp
@@ -0,0 +1,163 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Generated from the following GLSL
+#version 440 core
+layout(location = 0) out vec4 v;
+layout(location = 1) in vec4 in_val;
+void main() {
+ int i;
+ switch (int(in_val.x)) {
+ case 0:
+ i = 0;
+ case 1:
+ i = 1;
+ break;
+ case 2:
+ i = 2;
+ case 3:
+ i = 3;
+ case 4:
+ i = 4;
+ break;
+ default:
+ i = 0;
+ }
+ v = vec4(i, i, i, i);
+}
+*/
+TEST_F(PassClassTest, UnreachableNestedIfs) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %9 %35
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %9 "in_val"
+ OpName %25 "i"
+ OpName %35 "v"
+ OpDecorate %9 Location 1
+ OpDecorate %35 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Input %7
+ %9 = OpVariable %8 Input
+ %10 = OpTypeInt 32 0
+ %11 = OpConstant %10 0
+ %12 = OpTypePointer Input %6
+ %15 = OpTypeInt 32 1
+ %24 = OpTypePointer Function %15
+ %26 = OpConstant %15 0
+ %27 = OpConstant %15 1
+ %29 = OpConstant %15 2
+ %30 = OpConstant %15 3
+ %31 = OpConstant %15 4
+ %34 = OpTypePointer Output %7
+ %35 = OpVariable %34 Output
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %25 = OpVariable %24 Function
+ %13 = OpAccessChain %12 %9 %11
+ %14 = OpLoad %6 %13
+ %16 = OpConvertFToS %15 %14
+ OpSelectionMerge %23 None
+ OpSwitch %16 %22 0 %17 1 %18 2 %19 3 %20 4 %21
+ %22 = OpLabel
+ OpStore %25 %26
+ OpBranch %23
+ %17 = OpLabel
+ OpStore %25 %26
+ OpBranch %18
+ %18 = OpLabel
+ OpStore %25 %27
+ OpBranch %23
+ %19 = OpLabel
+ OpStore %25 %29
+ OpBranch %20
+ %20 = OpLabel
+ OpStore %25 %30
+ OpBranch %21
+ %21 = OpLabel
+ OpStore %25 %31
+ OpBranch %23
+ %23 = OpLabel
+ %36 = OpLoad %15 %25
+ %37 = OpConvertSToF %6 %36
+ %38 = OpLoad %15 %25
+ %39 = OpConvertSToF %6 %38
+ %40 = OpLoad %15 %25
+ %41 = OpConvertSToF %6 %40
+ %42 = OpLoad %15 %25
+ %43 = OpConvertSToF %6 %42
+ %44 = OpCompositeConstruct %7 %37 %39 %41 %43
+ OpStore %35 %44
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+ DominatorAnalysis* analysis = context->GetDominatorAnalysis(f);
+
+ EXPECT_TRUE(analysis->Dominates(5, 5));
+ EXPECT_TRUE(analysis->Dominates(5, 17));
+ EXPECT_TRUE(analysis->Dominates(5, 18));
+ EXPECT_TRUE(analysis->Dominates(5, 19));
+ EXPECT_TRUE(analysis->Dominates(5, 20));
+ EXPECT_TRUE(analysis->Dominates(5, 21));
+ EXPECT_TRUE(analysis->Dominates(5, 22));
+ EXPECT_TRUE(analysis->Dominates(5, 23));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 17));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 18));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 19));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 20));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 21));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 22));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 23));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/unreachable_for.cpp b/test/opt/dominator_tree/unreachable_for.cpp
new file mode 100644
index 0000000..469e5c1
--- /dev/null
+++ b/test/opt/dominator_tree/unreachable_for.cpp
@@ -0,0 +1,121 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Generated from the following GLSL
+#version 440 core
+void main() {
+ for (int i = 0; i < 1; i++) {
+ break;
+ }
+}
+*/
+TEST_F(PassClassTest, UnreachableNestedIfs) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 1
+ %17 = OpTypeBool
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %12
+ %13 = OpLabel
+ %20 = OpLoad %6 %8
+ %21 = OpIAdd %6 %20 %16
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+ DominatorAnalysis* analysis = context->GetDominatorAnalysis(f);
+ EXPECT_TRUE(analysis->Dominates(5, 5));
+ EXPECT_TRUE(analysis->Dominates(5, 10));
+ EXPECT_TRUE(analysis->Dominates(5, 14));
+ EXPECT_TRUE(analysis->Dominates(5, 11));
+ EXPECT_TRUE(analysis->Dominates(5, 12));
+ EXPECT_TRUE(analysis->Dominates(10, 10));
+ EXPECT_TRUE(analysis->Dominates(10, 14));
+ EXPECT_TRUE(analysis->Dominates(10, 11));
+ EXPECT_TRUE(analysis->Dominates(10, 12));
+ EXPECT_TRUE(analysis->Dominates(14, 14));
+ EXPECT_TRUE(analysis->Dominates(14, 11));
+ EXPECT_TRUE(analysis->Dominates(14, 12));
+ EXPECT_TRUE(analysis->Dominates(11, 11));
+ EXPECT_TRUE(analysis->Dominates(12, 12));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 10));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 14));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(5, 12));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 14));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 12));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 12));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/dominator_tree/unreachable_for_post.cpp b/test/opt/dominator_tree/unreachable_for_post.cpp
new file mode 100644
index 0000000..8d3e37b
--- /dev/null
+++ b/test/opt/dominator_tree/unreachable_for_post.cpp
@@ -0,0 +1,118 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/dominator_analysis.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Generated from the following GLSL
+#version 440 core
+void main() {
+ for (int i = 0; i < 1; i++) {
+ break;
+ }
+}
+*/
+TEST_F(PassClassTest, UnreachableNestedIfs) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 1
+ %17 = OpTypeBool
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %15 = OpLoad %6 %8
+ %18 = OpSLessThan %17 %15 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %12
+ %13 = OpLabel
+ %20 = OpLoad %6 %8
+ %21 = OpIAdd %6 %20 %16
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ const Function* f = spvtest::GetFunction(module, 4);
+
+ PostDominatorAnalysis* analysis = context->GetPostDominatorAnalysis(f);
+
+ EXPECT_TRUE(analysis->Dominates(12, 12));
+ EXPECT_TRUE(analysis->Dominates(12, 14));
+ EXPECT_TRUE(analysis->Dominates(12, 11));
+ EXPECT_TRUE(analysis->Dominates(12, 10));
+ EXPECT_TRUE(analysis->Dominates(12, 5));
+ EXPECT_TRUE(analysis->Dominates(14, 14));
+ EXPECT_TRUE(analysis->Dominates(14, 10));
+ EXPECT_TRUE(analysis->Dominates(14, 5));
+ EXPECT_TRUE(analysis->Dominates(10, 10));
+ EXPECT_TRUE(analysis->Dominates(10, 5));
+ EXPECT_TRUE(analysis->Dominates(5, 5));
+
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 14));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 11));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 10));
+ EXPECT_TRUE(analysis->StrictlyDominates(12, 5));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 10));
+ EXPECT_TRUE(analysis->StrictlyDominates(14, 5));
+ EXPECT_TRUE(analysis->StrictlyDominates(10, 5));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/eliminate_dead_const_test.cpp b/test/opt/eliminate_dead_const_test.cpp
new file mode 100644
index 0000000..7fac866
--- /dev/null
+++ b/test/opt/eliminate_dead_const_test.cpp
@@ -0,0 +1,847 @@
+// Copyright (c) 2016 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 <algorithm>
+#include <cstdarg>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using EliminateDeadConstantBasicTest = PassTest<::testing::Test>;
+
+TEST_F(EliminateDeadConstantBasicTest, BasicAllDeadConstants) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %main \"main\"",
+ "%void = OpTypeVoid",
+ "%4 = OpTypeFunction %void",
+ "%bool = OpTypeBool",
+ "%true = OpConstantTrue %bool",
+ "%false = OpConstantFalse %bool",
+ "%int = OpTypeInt 32 1",
+ "%9 = OpConstant %int 1",
+ "%uint = OpTypeInt 32 0",
+ "%11 = OpConstant %uint 2",
+ "%float = OpTypeFloat 32",
+ "%13 = OpConstant %float 3.1415",
+ "%double = OpTypeFloat 64",
+ "%15 = OpConstant %double 3.14159265358979",
+ "%main = OpFunction %void None %4",
+ "%16 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ // None of the above constants is ever used, so all of them should be
+ // eliminated.
+ const char* const_decl_opcodes[] = {
+ " OpConstantTrue ",
+ " OpConstantFalse ",
+ " OpConstant ",
+ };
+ // Skip lines that have any one of const_decl_opcodes.
+ const std::string expected_disassembly =
+ SelectiveJoin(text, [&const_decl_opcodes](const char* line) {
+ return std::any_of(
+ std::begin(const_decl_opcodes), std::end(const_decl_opcodes),
+ [&line](const char* const_decl_op) {
+ return std::string(line).find(const_decl_op) != std::string::npos;
+ });
+ });
+
+ SinglePassRunAndCheck<EliminateDeadConstantPass>(
+ JoinAllInsts(text), expected_disassembly, /* skip_nop = */ true);
+}
+
+TEST_F(EliminateDeadConstantBasicTest, BasicNoneDeadConstants) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %btv \"btv\"",
+ "OpName %bfv \"bfv\"",
+ "OpName %iv \"iv\"",
+ "OpName %uv \"uv\"",
+ "OpName %fv \"fv\"",
+ "OpName %dv \"dv\"",
+ "%void = OpTypeVoid",
+ "%10 = OpTypeFunction %void",
+ "%bool = OpTypeBool",
+ "%_ptr_Function_bool = OpTypePointer Function %bool",
+ "%true = OpConstantTrue %bool",
+ "%false = OpConstantFalse %bool",
+ "%int = OpTypeInt 32 1",
+ "%_ptr_Function_int = OpTypePointer Function %int",
+ "%int_1 = OpConstant %int 1",
+ "%uint = OpTypeInt 32 0",
+ "%_ptr_Function_uint = OpTypePointer Function %uint",
+ "%uint_2 = OpConstant %uint 2",
+ "%float = OpTypeFloat 32",
+ "%_ptr_Function_float = OpTypePointer Function %float",
+ "%float_3_1415 = OpConstant %float 3.1415",
+ "%double = OpTypeFloat 64",
+ "%_ptr_Function_double = OpTypePointer Function %double",
+ "%double_3_14159265358979 = OpConstant %double 3.14159265358979",
+ "%main = OpFunction %void None %10",
+ "%27 = OpLabel",
+ "%btv = OpVariable %_ptr_Function_bool Function",
+ "%bfv = OpVariable %_ptr_Function_bool Function",
+ "%iv = OpVariable %_ptr_Function_int Function",
+ "%uv = OpVariable %_ptr_Function_uint Function",
+ "%fv = OpVariable %_ptr_Function_float Function",
+ "%dv = OpVariable %_ptr_Function_double Function",
+ "OpStore %btv %true",
+ "OpStore %bfv %false",
+ "OpStore %iv %int_1",
+ "OpStore %uv %uint_2",
+ "OpStore %fv %float_3_1415",
+ "OpStore %dv %double_3_14159265358979",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ // All constants are used, so none of them should be eliminated.
+ SinglePassRunAndCheck<EliminateDeadConstantPass>(
+ JoinAllInsts(text), JoinAllInsts(text), /* skip_nop = */ true);
+}
+
+struct EliminateDeadConstantTestCase {
+ // Type declarations and constants that should be kept.
+ std::vector<std::string> used_consts;
+ // Instructions that refer to constants, this is added to create uses for
+ // some constants so they won't be treated as dead constants.
+ std::vector<std::string> main_insts;
+ // Dead constants that should be removed.
+ std::vector<std::string> dead_consts;
+};
+
+// All types that are potentially required in EliminateDeadConstantTest.
+const std::vector<std::string> CommonTypes = {
+ // clang-format off
+ // scalar types
+ "%bool = OpTypeBool",
+ "%uint = OpTypeInt 32 0",
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%double = OpTypeFloat 64",
+ // vector types
+ "%v2bool = OpTypeVector %bool 2",
+ "%v2uint = OpTypeVector %uint 2",
+ "%v2int = OpTypeVector %int 2",
+ "%v3int = OpTypeVector %int 3",
+ "%v4int = OpTypeVector %int 4",
+ "%v2float = OpTypeVector %float 2",
+ "%v3float = OpTypeVector %float 3",
+ "%v2double = OpTypeVector %double 2",
+ // variable pointer types
+ "%_pf_bool = OpTypePointer Function %bool",
+ "%_pf_uint = OpTypePointer Function %uint",
+ "%_pf_int = OpTypePointer Function %int",
+ "%_pf_float = OpTypePointer Function %float",
+ "%_pf_double = OpTypePointer Function %double",
+ "%_pf_v2int = OpTypePointer Function %v2int",
+ "%_pf_v3int = OpTypePointer Function %v3int",
+ "%_pf_v2float = OpTypePointer Function %v2float",
+ "%_pf_v3float = OpTypePointer Function %v3float",
+ "%_pf_v2double = OpTypePointer Function %v2double",
+ // struct types
+ "%inner_struct = OpTypeStruct %bool %int %float %double",
+ "%outer_struct = OpTypeStruct %inner_struct %int %double",
+ "%flat_struct = OpTypeStruct %bool %int %float %double",
+ // clang-format on
+};
+
+using EliminateDeadConstantTest =
+ PassTest<::testing::TestWithParam<EliminateDeadConstantTestCase>>;
+
+TEST_P(EliminateDeadConstantTest, Custom) {
+ auto& tc = GetParam();
+ AssemblyBuilder builder;
+ builder.AppendTypesConstantsGlobals(CommonTypes)
+ .AppendTypesConstantsGlobals(tc.used_consts)
+ .AppendInMain(tc.main_insts);
+ const std::string expected = builder.GetCode();
+ builder.AppendTypesConstantsGlobals(tc.dead_consts);
+ const std::string assembly_with_dead_const = builder.GetCode();
+ SinglePassRunAndCheck<EliminateDeadConstantPass>(
+ assembly_with_dead_const, expected, /* skip_nop = */ true);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ScalarTypeConstants, EliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+ // clang-format off
+ // Scalar type constants, one dead constant and one used constant.
+ {
+ /* .used_consts = */
+ {
+ "%used_const_int = OpConstant %int 1",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %used_const_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_int = OpConstant %int 1",
+ },
+ },
+ {
+ /* .used_consts = */
+ {
+ "%used_const_uint = OpConstant %uint 1",
+ },
+ /* .main_insts = */
+ {
+ "%uint_var = OpVariable %_pf_uint Function",
+ "OpStore %uint_var %used_const_uint",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_uint = OpConstant %uint 1",
+ },
+ },
+ {
+ /* .used_consts = */
+ {
+ "%used_const_float = OpConstant %float 3.1415",
+ },
+ /* .main_insts = */
+ {
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %used_const_float",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_float = OpConstant %float 3.1415",
+ },
+ },
+ {
+ /* .used_consts = */
+ {
+ "%used_const_double = OpConstant %double 3.141592653",
+ },
+ /* .main_insts = */
+ {
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %double_var %used_const_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_const_double = OpConstant %double 3.141592653",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ VectorTypeConstants, EliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+ // clang-format off
+ // Tests eliminating dead constant type ivec2. One dead constant vector
+ // and one used constant vector, each built from its own group of
+ // scalar constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_int_x = OpConstant %int 1",
+ "%used_int_y = OpConstant %int 2",
+ "%used_v2int = OpConstantComposite %v2int %used_int_x %used_int_y",
+ },
+ /* .main_insts = */
+ {
+ "%v2int_var = OpVariable %_pf_v2int Function",
+ "OpStore %v2int_var %used_v2int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int_x = OpConstant %int 1",
+ "%dead_int_y = OpConstant %int 2",
+ "%dead_v2int = OpConstantComposite %v2int %dead_int_x %dead_int_y",
+ },
+ },
+ // Tests eliminating dead constant ivec2. One dead constant vector and
+ // one used constant vector. But both built from a same group of
+ // scalar constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_int_x = OpConstant %int 1",
+ "%used_int_y = OpConstant %int 2",
+ "%used_int_z = OpConstant %int 3",
+ "%used_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z",
+ },
+ /* .main_insts = */
+ {
+ "%v3int_var = OpVariable %_pf_v3int Function",
+ "OpStore %v3int_var %used_v3int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z",
+ },
+ },
+ // Tests eliminating dead cosntant vec2. One dead constant vector and
+ // one used constant vector. Each built from its own group of scalar
+ // constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_float_x = OpConstant %float 3.1415",
+ "%used_float_y = OpConstant %float 4.25",
+ "%used_v2float = OpConstantComposite %v2float %used_float_x %used_float_y",
+ },
+ /* .main_insts = */
+ {
+ "%v2float_var = OpVariable %_pf_v2float Function",
+ "OpStore %v2float_var %used_v2float",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_float_x = OpConstant %float 3.1415",
+ "%dead_float_y = OpConstant %float 4.25",
+ "%dead_v2float = OpConstantComposite %v2float %dead_float_x %dead_float_y",
+ },
+ },
+ // Tests eliminating dead cosntant vec2. One dead constant vector and
+ // one used constant vector. Both built from a same group of scalar
+ // constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_float_x = OpConstant %float 3.1415",
+ "%used_float_y = OpConstant %float 4.25",
+ "%used_float_z = OpConstant %float 4.75",
+ "%used_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z",
+ },
+ /* .main_insts = */
+ {
+ "%v3float_var = OpVariable %_pf_v3float Function",
+ "OpStore %v3float_var %used_v3float",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ StructTypeConstants, EliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+ // clang-format off
+ // A plain struct type dead constants. All of its components are dead
+ // constants too.
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_int = OpConstant %int 1",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_double = OpConstant %double 3.14159265358979",
+ "%dead_struct = OpConstantComposite %flat_struct %dead_bool %dead_int %dead_float %dead_double",
+ },
+ },
+ // A plain struct type dead constants. Some of its components are dead
+ // constants while others are not.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 1",
+ "%used_double = OpConstant %double 3.14159265358979",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %used_int",
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %double_var %used_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_struct = OpConstantComposite %flat_struct %dead_bool %used_int %dead_float %used_double",
+ },
+ },
+ // A nesting struct type dead constants. All components of both outer
+ // and inner structs are dead and should be removed after dead constant
+ // elimination.
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_int = OpConstant %int 1",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_double = OpConstant %double 3.1415926535",
+ "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %dead_int %dead_float %dead_double",
+ "%dead_int2 = OpConstant %int 2",
+ "%dead_double2 = OpConstant %double 1.428571428514",
+ "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int2 %dead_double2",
+ },
+ },
+ // A nesting struct type dead constants. Some of its components are
+ // dead constants while others are not.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 1",
+ "%used_double = OpConstant %double 3.14159265358979",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %used_int",
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %double_var %used_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpConstantTrue %bool",
+ "%dead_float = OpConstant %float 2.5",
+ "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %used_int %dead_float %used_double",
+ "%dead_int = OpConstant %int 2",
+ "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int %used_double",
+ },
+ },
+ // A nesting struct case. The inner struct is used while the outer struct is not
+ {
+ /* .used_const = */
+ {
+ "%used_bool = OpConstantTrue %bool",
+ "%used_int = OpConstant %int 1",
+ "%used_float = OpConstant %float 1.25",
+ "%used_double = OpConstant %double 1.23456789012345",
+ "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+ },
+ /* .main_insts = */
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "%bool_from_inner_struct = OpCompositeExtract %bool %used_inner_struct 0",
+ "OpStore %bool_var %bool_from_inner_struct",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int = OpConstant %int 2",
+ "%dead_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %dead_int %used_double"
+ },
+ },
+ // A nesting struct case. The outer struct is used, so the inner struct should not
+ // be removed even though it is not used anywhere.
+ {
+ /* .used_const = */
+ {
+ "%used_bool = OpConstantTrue %bool",
+ "%used_int = OpConstant %int 1",
+ "%used_float = OpConstant %float 1.25",
+ "%used_double = OpConstant %double 1.23456789012345",
+ "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+ "%used_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double"
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "%int_from_outer_struct = OpCompositeExtract %int %used_outer_struct 1",
+ "OpStore %int_var %int_from_outer_struct",
+ },
+ /* .dead_consts = */ {},
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ ScalarTypeSpecConstants, EliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+ // clang-format off
+ // All scalar type spec constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_bool = OpSpecConstantTrue %bool",
+ "%used_uint = OpSpecConstant %uint 2",
+ "%used_int = OpSpecConstant %int 2",
+ "%used_float = OpSpecConstant %float 2.5",
+ "%used_double = OpSpecConstant %double 1.42857142851",
+ },
+ /* .main_insts = */
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "%uint_var = OpVariable %_pf_uint Function",
+ "%int_var = OpVariable %_pf_int Function",
+ "%float_var = OpVariable %_pf_float Function",
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %bool_var %used_bool", "OpStore %uint_var %used_uint",
+ "OpStore %int_var %used_int", "OpStore %float_var %used_float",
+ "OpStore %double_var %used_double",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpSpecConstantTrue %bool",
+ "%dead_uint = OpSpecConstant %uint 2",
+ "%dead_int = OpSpecConstant %int 2",
+ "%dead_float = OpSpecConstant %float 2.5",
+ "%dead_double = OpSpecConstant %double 1.42857142851",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ VectorTypeSpecConstants, EliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+ // clang-format off
+ // Bool vector type spec constants. One vector has all component dead,
+ // another vector has one dead boolean and one used boolean.
+ {
+ /* .used_consts = */
+ {
+ "%used_bool = OpSpecConstantTrue %bool",
+ },
+ /* .main_insts = */
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "OpStore %bool_var %used_bool",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_bool = OpSpecConstantFalse %bool",
+ "%dead_bool_vec1 = OpSpecConstantComposite %v2bool %dead_bool %dead_bool",
+ "%dead_bool_vec2 = OpSpecConstantComposite %v2bool %dead_bool %used_bool",
+ },
+ },
+
+ // Uint vector type spec constants. One vector has all component dead,
+ // another vector has one dead unsigend integer and one used unsigned
+ // integer.
+ {
+ /* .used_consts = */
+ {
+ "%used_uint = OpSpecConstant %uint 3",
+ },
+ /* .main_insts = */
+ {
+ "%uint_var = OpVariable %_pf_uint Function",
+ "OpStore %uint_var %used_uint",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_uint = OpSpecConstant %uint 1",
+ "%dead_uint_vec1 = OpSpecConstantComposite %v2uint %dead_uint %dead_uint",
+ "%dead_uint_vec2 = OpSpecConstantComposite %v2uint %dead_uint %used_uint",
+ },
+ },
+
+ // Int vector type spec constants. One vector has all component dead,
+ // another vector has one dead integer and one used integer.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpSpecConstant %int 3",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %used_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int = OpSpecConstant %int 1",
+ "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_int %dead_int",
+ "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_int %used_int",
+ },
+ },
+
+ // Int vector type spec constants built with both spec constants and
+ // front-end constants.
+ {
+ /* .used_consts = */
+ {
+ "%used_spec_int = OpSpecConstant %int 3",
+ "%used_front_end_int = OpConstant %int 3",
+ },
+ /* .main_insts = */
+ {
+ "%int_var1 = OpVariable %_pf_int Function",
+ "OpStore %int_var1 %used_spec_int",
+ "%int_var2 = OpVariable %_pf_int Function",
+ "OpStore %int_var2 %used_front_end_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_spec_int = OpSpecConstant %int 1",
+ "%dead_front_end_int = OpConstant %int 1",
+ // Dead front-end and dead spec constants
+ "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_spec_int %dead_front_end_int",
+ // Used front-end and dead spec constants
+ "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_spec_int %used_front_end_int",
+ // Dead front-end and used spec constants
+ "%dead_int_vec3 = OpSpecConstantComposite %v2int %dead_front_end_int %used_spec_int",
+ },
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ SpecConstantOp, EliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+ // clang-format off
+ // Cast operations: uint <-> int <-> bool
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ // Assistant constants, only used in dead spec constant
+ // operations.
+ "%signed_zero = OpConstant %int 0",
+ "%signed_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero",
+ "%unsigned_zero = OpConstant %uint 0",
+ "%unsigned_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+ "%signed_one = OpConstant %int 1",
+ "%signed_one_vec = OpConstantComposite %v2int %signed_one %signed_one",
+ "%unsigned_one = OpConstant %uint 1",
+ "%unsigned_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+
+ // Spec constants that support casting to each other.
+ "%dead_bool = OpSpecConstantTrue %bool",
+ "%dead_uint = OpSpecConstant %uint 1",
+ "%dead_int = OpSpecConstant %int 2",
+ "%dead_bool_vec = OpSpecConstantComposite %v2bool %dead_bool %dead_bool",
+ "%dead_uint_vec = OpSpecConstantComposite %v2uint %dead_uint %dead_uint",
+ "%dead_int_vec = OpSpecConstantComposite %v2int %dead_int %dead_int",
+
+ // Scalar cast to boolean spec constant.
+ "%int_to_bool = OpSpecConstantOp %bool INotEqual %dead_int %signed_zero",
+ "%uint_to_bool = OpSpecConstantOp %bool INotEqual %dead_uint %unsigned_zero",
+
+ // Vector cast to boolean spec constant.
+ "%int_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_int_vec %signed_zero_vec",
+ "%uint_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_uint_vec %unsigned_zero_vec",
+
+ // Scalar cast to int spec constant.
+ "%bool_to_int = OpSpecConstantOp %int Select %dead_bool %signed_one %signed_zero",
+ "%uint_to_int = OpSpecConstantOp %uint IAdd %dead_uint %unsigned_zero",
+
+ // Vector cast to int spec constant.
+ "%bool_to_int_vec = OpSpecConstantOp %v2int Select %dead_bool_vec %signed_one_vec %signed_zero_vec",
+ "%uint_to_int_vec = OpSpecConstantOp %v2uint IAdd %dead_uint_vec %unsigned_zero_vec",
+
+ // Scalar cast to uint spec constant.
+ "%bool_to_uint = OpSpecConstantOp %uint Select %dead_bool %unsigned_one %unsigned_zero",
+ "%int_to_uint_vec = OpSpecConstantOp %uint IAdd %dead_int %signed_zero",
+
+ // Vector cast to uint spec constant.
+ "%bool_to_uint_vec = OpSpecConstantOp %v2uint Select %dead_bool_vec %unsigned_one_vec %unsigned_zero_vec",
+ "%int_to_uint = OpSpecConstantOp %v2uint IAdd %dead_int_vec %signed_zero_vec",
+ },
+ },
+
+ // Add, sub, mul, div, rem.
+ {
+ /* .used_consts = */ {},
+ /* .main_insts = */ {},
+ /* .dead_consts = */
+ {
+ "%dead_spec_int_a = OpSpecConstant %int 1",
+ "%dead_spec_int_a_vec = OpSpecConstantComposite %v2int %dead_spec_int_a %dead_spec_int_a",
+
+ "%dead_spec_int_b = OpSpecConstant %int 2",
+ "%dead_spec_int_b_vec = OpSpecConstantComposite %v2int %dead_spec_int_b %dead_spec_int_b",
+
+ "%dead_const_int_c = OpConstant %int 3",
+ "%dead_const_int_c_vec = OpConstantComposite %v2int %dead_const_int_c %dead_const_int_c",
+
+ // Add
+ "%add_a_b = OpSpecConstantOp %int IAdd %dead_spec_int_a %dead_spec_int_b",
+ "%add_a_b_vec = OpSpecConstantOp %v2int IAdd %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Sub
+ "%sub_a_b = OpSpecConstantOp %int ISub %dead_spec_int_a %dead_spec_int_b",
+ "%sub_a_b_vec = OpSpecConstantOp %v2int ISub %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Mul
+ "%mul_a_b = OpSpecConstantOp %int IMul %dead_spec_int_a %dead_spec_int_b",
+ "%mul_a_b_vec = OpSpecConstantOp %v2int IMul %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Div
+ "%div_a_b = OpSpecConstantOp %int SDiv %dead_spec_int_a %dead_spec_int_b",
+ "%div_a_b_vec = OpSpecConstantOp %v2int SDiv %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Bitwise Xor
+ "%xor_a_b = OpSpecConstantOp %int BitwiseXor %dead_spec_int_a %dead_spec_int_b",
+ "%xor_a_b_vec = OpSpecConstantOp %v2int BitwiseXor %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+ // Scalar Comparison
+ "%less_a_b = OpSpecConstantOp %bool SLessThan %dead_spec_int_a %dead_spec_int_b",
+ },
+ },
+
+ // Vectors without used swizzles should be removed.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 3",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %used_int",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int = OpConstant %int 3",
+
+ "%dead_spec_int_a = OpSpecConstant %int 1",
+ "%vec_a = OpSpecConstantComposite %v4int %dead_spec_int_a %dead_spec_int_a %dead_int %dead_int",
+
+ "%dead_spec_int_b = OpSpecConstant %int 2",
+ "%vec_b = OpSpecConstantComposite %v4int %dead_spec_int_b %dead_spec_int_b %used_int %used_int",
+
+ // Extract scalar
+ "%a_x = OpSpecConstantOp %int CompositeExtract %vec_a 0",
+ "%b_x = OpSpecConstantOp %int CompositeExtract %vec_b 0",
+
+ // Extract vector
+ "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1",
+ "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1",
+ },
+ },
+ // Vectors with used swizzles should not be removed.
+ {
+ /* .used_consts = */
+ {
+ "%used_int = OpConstant %int 3",
+ "%used_spec_int_a = OpSpecConstant %int 1",
+ "%used_spec_int_b = OpSpecConstant %int 2",
+ // Create vectors
+ "%vec_a = OpSpecConstantComposite %v4int %used_spec_int_a %used_spec_int_a %used_int %used_int",
+ "%vec_b = OpSpecConstantComposite %v4int %used_spec_int_b %used_spec_int_b %used_int %used_int",
+ // Extract vector
+ "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1",
+ "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1",
+ },
+ /* .main_insts = */
+ {
+ "%v2int_var_a = OpVariable %_pf_v2int Function",
+ "%v2int_var_b = OpVariable %_pf_v2int Function",
+ "OpStore %v2int_var_a %a_xy",
+ "OpStore %v2int_var_b %b_xy",
+ },
+ /* .dead_consts = */ {},
+ },
+ // clang-format on
+ })));
+
+INSTANTIATE_TEST_CASE_P(
+ LongDefUseChain, EliminateDeadConstantTest,
+ ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+ // clang-format off
+ // Long Def-Use chain with binary operations.
+ {
+ /* .used_consts = */
+ {
+ "%array_size = OpConstant %int 4",
+ "%type_arr_int_4 = OpTypeArray %int %array_size",
+ "%used_int_0 = OpConstant %int 100",
+ "%used_int_1 = OpConstant %int 1",
+ "%used_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_1",
+ "%used_int_3 = OpSpecConstantOp %int ISub %used_int_0 %used_int_2",
+ "%used_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_3",
+ "%used_int_5 = OpSpecConstantOp %int ISub %used_int_0 %used_int_4",
+ "%used_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_5",
+ "%used_int_7 = OpSpecConstantOp %int ISub %used_int_0 %used_int_6",
+ "%used_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_7",
+ "%used_int_9 = OpSpecConstantOp %int ISub %used_int_0 %used_int_8",
+ "%used_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_9",
+ "%used_int_11 = OpSpecConstantOp %int ISub %used_int_0 %used_int_10",
+ "%used_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_11",
+ "%used_int_13 = OpSpecConstantOp %int ISub %used_int_0 %used_int_12",
+ "%used_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_13",
+ "%used_int_15 = OpSpecConstantOp %int ISub %used_int_0 %used_int_14",
+ "%used_int_16 = OpSpecConstantOp %int ISub %used_int_0 %used_int_15",
+ "%used_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_16",
+ "%used_int_18 = OpSpecConstantOp %int ISub %used_int_0 %used_int_17",
+ "%used_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_18",
+ "%used_int_20 = OpSpecConstantOp %int ISub %used_int_0 %used_int_19",
+ "%used_vec_a = OpSpecConstantComposite %v2int %used_int_18 %used_int_19",
+ "%used_vec_b = OpSpecConstantOp %v2int IMul %used_vec_a %used_vec_a",
+ "%used_int_21 = OpSpecConstantOp %int CompositeExtract %used_vec_b 0",
+ "%used_array = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21",
+ },
+ /* .main_insts = */
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "%used_array_2 = OpCompositeExtract %int %used_array 2",
+ "OpStore %int_var %used_array_2",
+ },
+ /* .dead_consts = */
+ {
+ "%dead_int_1 = OpConstant %int 2",
+ "%dead_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_1",
+ "%dead_int_3 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_2",
+ "%dead_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_3",
+ "%dead_int_5 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_4",
+ "%dead_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_5",
+ "%dead_int_7 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_6",
+ "%dead_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_7",
+ "%dead_int_9 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_8",
+ "%dead_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_9",
+ "%dead_int_11 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_10",
+ "%dead_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_11",
+ "%dead_int_13 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_12",
+ "%dead_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_13",
+ "%dead_int_15 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_14",
+ "%dead_int_16 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_15",
+ "%dead_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_16",
+ "%dead_int_18 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_17",
+ "%dead_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_18",
+ "%dead_int_20 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_19",
+ "%dead_vec_a = OpSpecConstantComposite %v2int %dead_int_18 %dead_int_19",
+ "%dead_vec_b = OpSpecConstantOp %v2int IMul %dead_vec_a %dead_vec_a",
+ "%dead_int_21 = OpSpecConstantOp %int CompositeExtract %dead_vec_b 0",
+ "%dead_array = OpConstantComposite %type_arr_int_4 %dead_int_20 %used_int_20 %dead_int_19 %used_int_19",
+ },
+ },
+ // Long Def-Use chain with swizzle
+ // clang-format on
+ })));
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/eliminate_dead_functions_test.cpp b/test/opt/eliminate_dead_functions_test.cpp
new file mode 100644
index 0000000..0a3d490
--- /dev/null
+++ b/test/opt/eliminate_dead_functions_test.cpp
@@ -0,0 +1,209 @@
+// 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 <vector>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::HasSubstr;
+using EliminateDeadFunctionsBasicTest = PassTest<::testing::Test>;
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicDeleteDeadFunction) {
+ // The function Dead should be removed because it is never called.
+ const std::vector<const char*> common_code = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %Live \"Live\"",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "%16 = OpFunctionCall %void %Live",
+ "%17 = OpFunctionCall %void %Live",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ const std::vector<const char*> dead_function = {
+ // clang-format off
+ "%Dead = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<EliminateDeadFunctionsPass>(
+ JoinAllInsts(Concat(common_code, dead_function)),
+ JoinAllInsts(common_code), /* skip_nop = */ true);
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepLiveFunction) {
+ // Everything is reachable from an entry point, so no functions should be
+ // deleted.
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %Live1 \"Live1\"",
+ "OpName %Live2 \"Live2\"",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "%16 = OpFunctionCall %void %Live2",
+ "%17 = OpFunctionCall %void %Live1",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live1 = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live2 = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ std::string assembly = JoinAllInsts(text);
+ auto result = SinglePassRunAndDisassemble<EliminateDeadFunctionsPass>(
+ assembly, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+ EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicKeepExportFunctions) {
+ // All functions are reachable. In particular, ExportedFunc and Constant are
+ // reachable because ExportedFunc is exported. Nothing should be removed.
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Linkage",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\"",
+ "OpName %main \"main\"",
+ "OpName %ExportedFunc \"ExportedFunc\"",
+ "OpName %Live \"Live\"",
+ "OpDecorate %ExportedFunc LinkageAttributes \"ExportedFunc\" Export",
+ "%void = OpTypeVoid",
+ "%7 = OpTypeFunction %void",
+ "%main = OpFunction %void None %7",
+ "%15 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+"%ExportedFunc = OpFunction %void None %7",
+ "%19 = OpLabel",
+ "%16 = OpFunctionCall %void %Live",
+ "OpReturn",
+ "OpFunctionEnd",
+ "%Live = OpFunction %void None %7",
+ "%20 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ std::string assembly = JoinAllInsts(text);
+ auto result = SinglePassRunAndDisassemble<EliminateDeadFunctionsPass>(
+ assembly, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+ EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(EliminateDeadFunctionsBasicTest, BasicRemoveDecorationsAndNames) {
+ // We want to remove the names and decorations associated with results that
+ // are removed. This test will check for that.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ OpName %Dead "Dead"
+ OpName %x "x"
+ OpName %y "y"
+ OpName %z "z"
+ OpDecorate %x RelaxedPrecision
+ OpDecorate %y RelaxedPrecision
+ OpDecorate %z RelaxedPrecision
+ OpDecorate %6 RelaxedPrecision
+ OpDecorate %7 RelaxedPrecision
+ OpDecorate %8 RelaxedPrecision
+ %void = OpTypeVoid
+ %10 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+ %float_1 = OpConstant %float 1
+ %main = OpFunction %void None %10
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %10
+ %15 = OpLabel
+ %x = OpVariable %_ptr_Function_float Function
+ %y = OpVariable %_ptr_Function_float Function
+ %z = OpVariable %_ptr_Function_float Function
+ OpStore %x %float_1
+ OpStore %y %float_1
+ %6 = OpLoad %float %x
+ %7 = OpLoad %float %y
+ %8 = OpFAdd %float %6 %7
+ OpStore %z %8
+ OpReturn
+ OpFunctionEnd)";
+
+ const std::string expected_output = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_1 = OpConstant %float 1
+%main = OpFunction %void None %10
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<EliminateDeadFunctionsPass>(text, expected_output,
+ /* skip_nop = */ true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/feature_manager_test.cpp b/test/opt/feature_manager_test.cpp
new file mode 100644
index 0000000..767376c
--- /dev/null
+++ b/test/opt/feature_manager_test.cpp
@@ -0,0 +1,142 @@
+// 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 <algorithm>
+#include <memory>
+#include <string>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FeatureManagerTest = ::testing::Test;
+
+TEST_F(FeatureManagerTest, MissingExtension) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ASSERT_NE(context, nullptr);
+
+ EXPECT_FALSE(context->get_feature_mgr()->HasExtension(
+ Extension::kSPV_KHR_variable_pointers));
+}
+
+TEST_F(FeatureManagerTest, OneExtension) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpExtension "SPV_KHR_variable_pointers"
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ASSERT_NE(context, nullptr);
+
+ EXPECT_TRUE(context->get_feature_mgr()->HasExtension(
+ Extension::kSPV_KHR_variable_pointers));
+}
+
+TEST_F(FeatureManagerTest, NotADifferentExtension) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpExtension "SPV_KHR_variable_pointers"
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ASSERT_NE(context, nullptr);
+
+ EXPECT_FALSE(context->get_feature_mgr()->HasExtension(
+ Extension::kSPV_KHR_storage_buffer_storage_class));
+}
+
+TEST_F(FeatureManagerTest, TwoExtensions) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpExtension "SPV_KHR_variable_pointers"
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ASSERT_NE(context, nullptr);
+
+ EXPECT_TRUE(context->get_feature_mgr()->HasExtension(
+ Extension::kSPV_KHR_variable_pointers));
+ EXPECT_TRUE(context->get_feature_mgr()->HasExtension(
+ Extension::kSPV_KHR_storage_buffer_storage_class));
+}
+
+// Test capability checks.
+TEST_F(FeatureManagerTest, ExplicitlyPresent1) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ASSERT_NE(context, nullptr);
+
+ EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityShader));
+ EXPECT_FALSE(context->get_feature_mgr()->HasCapability(SpvCapabilityKernel));
+}
+
+TEST_F(FeatureManagerTest, ExplicitlyPresent2) {
+ const std::string text = R"(
+OpCapability Kernel
+OpMemoryModel Logical GLSL450
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ASSERT_NE(context, nullptr);
+
+ EXPECT_FALSE(context->get_feature_mgr()->HasCapability(SpvCapabilityShader));
+ EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityKernel));
+}
+
+TEST_F(FeatureManagerTest, ImplicitlyPresent) {
+ const std::string text = R"(
+OpCapability Tessellation
+OpMemoryModel Logical GLSL450
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ASSERT_NE(context, nullptr);
+
+ // Check multiple levels of indirection. Tessellation implies Shader, which
+ // implies Matrix.
+ EXPECT_TRUE(
+ context->get_feature_mgr()->HasCapability(SpvCapabilityTessellation));
+ EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityShader));
+ EXPECT_TRUE(context->get_feature_mgr()->HasCapability(SpvCapabilityMatrix));
+ EXPECT_FALSE(context->get_feature_mgr()->HasCapability(SpvCapabilityKernel));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/flatten_decoration_test.cpp b/test/opt/flatten_decoration_test.cpp
new file mode 100644
index 0000000..fcf2341
--- /dev/null
+++ b/test/opt/flatten_decoration_test.cpp
@@ -0,0 +1,239 @@
+// 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 <vector>
+
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+// Returns the initial part of the assembly text for a valid
+// SPIR-V module, including instructions prior to decorations.
+std::string PreambleAssembly() {
+ return
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %hue %saturation %value
+OpExecutionMode %main OriginUpperLeft
+OpName %main "main"
+OpName %void_fn "void_fn"
+OpName %hue "hue"
+OpName %saturation "saturation"
+OpName %value "value"
+OpName %entry "entry"
+OpName %Point "Point"
+OpName %Camera "Camera"
+)";
+}
+
+// Retuns types
+std::string TypesAndFunctionsAssembly() {
+ return
+ R"(%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%float = OpTypeFloat 32
+%Point = OpTypeStruct %float %float %float
+%Camera = OpTypeStruct %float %float
+%_ptr_Input_float = OpTypePointer Input %float
+%hue = OpVariable %_ptr_Input_float Input
+%saturation = OpVariable %_ptr_Input_float Input
+%value = OpVariable %_ptr_Input_float Input
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+}
+
+struct FlattenDecorationCase {
+ // Names and decorations before the pass.
+ std::string input;
+ // Names and decorations after the pass.
+ std::string expected;
+};
+
+using FlattenDecorationTest =
+ PassTest<::testing::TestWithParam<FlattenDecorationCase>>;
+
+TEST_P(FlattenDecorationTest, TransformsDecorations) {
+ const auto before =
+ PreambleAssembly() + GetParam().input + TypesAndFunctionsAssembly();
+ const auto after =
+ PreambleAssembly() + GetParam().expected + TypesAndFunctionsAssembly();
+
+ SinglePassRunAndCheck<FlattenDecorationPass>(before, after, false, true);
+}
+
+INSTANTIATE_TEST_CASE_P(NoUses, FlattenDecorationTest,
+ ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+ // No OpDecorationGroup
+ {"", ""},
+
+ // OpDecorationGroup without any uses, and
+ // no OpName.
+ {"%group = OpDecorationGroup\n", ""},
+
+ // OpDecorationGroup without any uses, and
+ // with OpName targeting it. Proves you must
+ // remove the names as well.
+ {"OpName %group \"group\"\n"
+ "%group = OpDecorationGroup\n",
+ ""},
+
+ // OpDecorationGroup with decorations that
+ // target it, but no uses in OpGroupDecorate
+ // or OpGroupMemberDecorate instructions.
+ {"OpDecorate %group Flat\n"
+ "OpDecorate %group NoPerspective\n"
+ "%group = OpDecorationGroup\n",
+ ""},
+ }), );
+
+INSTANTIATE_TEST_CASE_P(OpGroupDecorate, FlattenDecorationTest,
+ ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+ // One OpGroupDecorate
+ {"OpName %group \"group\"\n"
+ "OpDecorate %group Flat\n"
+ "OpDecorate %group NoPerspective\n"
+ "%group = OpDecorationGroup\n"
+ "OpGroupDecorate %group %hue %saturation\n",
+ "OpDecorate %hue Flat\n"
+ "OpDecorate %saturation Flat\n"
+ "OpDecorate %hue NoPerspective\n"
+ "OpDecorate %saturation NoPerspective\n"},
+ // Multiple OpGroupDecorate
+ {"OpName %group \"group\"\n"
+ "OpDecorate %group Flat\n"
+ "OpDecorate %group NoPerspective\n"
+ "%group = OpDecorationGroup\n"
+ "OpGroupDecorate %group %hue %value\n"
+ "OpGroupDecorate %group %saturation\n",
+ "OpDecorate %hue Flat\n"
+ "OpDecorate %value Flat\n"
+ "OpDecorate %saturation Flat\n"
+ "OpDecorate %hue NoPerspective\n"
+ "OpDecorate %value NoPerspective\n"
+ "OpDecorate %saturation NoPerspective\n"},
+ // Two group decorations, interleaved
+ {"OpName %group0 \"group0\"\n"
+ "OpName %group1 \"group1\"\n"
+ "OpDecorate %group0 Flat\n"
+ "OpDecorate %group1 NoPerspective\n"
+ "%group0 = OpDecorationGroup\n"
+ "%group1 = OpDecorationGroup\n"
+ "OpGroupDecorate %group0 %hue %value\n"
+ "OpGroupDecorate %group1 %saturation\n",
+ "OpDecorate %hue Flat\n"
+ "OpDecorate %value Flat\n"
+ "OpDecorate %saturation NoPerspective\n"},
+ // Decoration with operands
+ {"OpName %group \"group\"\n"
+ "OpDecorate %group Location 42\n"
+ "%group = OpDecorationGroup\n"
+ "OpGroupDecorate %group %hue %saturation\n",
+ "OpDecorate %hue Location 42\n"
+ "OpDecorate %saturation Location 42\n"},
+ }), );
+
+INSTANTIATE_TEST_CASE_P(OpGroupMemberDecorate, FlattenDecorationTest,
+ ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+ // One OpGroupMemberDecorate
+ {"OpName %group \"group\"\n"
+ "OpDecorate %group Flat\n"
+ "OpDecorate %group Offset 16\n"
+ "%group = OpDecorationGroup\n"
+ "OpGroupMemberDecorate %group %Point 1\n",
+ "OpMemberDecorate %Point 1 Flat\n"
+ "OpMemberDecorate %Point 1 Offset 16\n"},
+ // Multiple OpGroupMemberDecorate using the same
+ // decoration group.
+ {"OpName %group \"group\"\n"
+ "OpDecorate %group Flat\n"
+ "OpDecorate %group NoPerspective\n"
+ "OpDecorate %group Offset 8\n"
+ "%group = OpDecorationGroup\n"
+ "OpGroupMemberDecorate %group %Point 2\n"
+ "OpGroupMemberDecorate %group %Camera 1\n",
+ "OpMemberDecorate %Point 2 Flat\n"
+ "OpMemberDecorate %Camera 1 Flat\n"
+ "OpMemberDecorate %Point 2 NoPerspective\n"
+ "OpMemberDecorate %Camera 1 NoPerspective\n"
+ "OpMemberDecorate %Point 2 Offset 8\n"
+ "OpMemberDecorate %Camera 1 Offset 8\n"},
+ // Two groups of member decorations, interleaved.
+ // Decoration is with and without operands.
+ {"OpName %group0 \"group0\"\n"
+ "OpName %group1 \"group1\"\n"
+ "OpDecorate %group0 Flat\n"
+ "OpDecorate %group0 Offset 8\n"
+ "OpDecorate %group1 NoPerspective\n"
+ "OpDecorate %group1 Offset 16\n"
+ "%group0 = OpDecorationGroup\n"
+ "%group1 = OpDecorationGroup\n"
+ "OpGroupMemberDecorate %group0 %Point 0\n"
+ "OpGroupMemberDecorate %group1 %Point 2\n",
+ "OpMemberDecorate %Point 0 Flat\n"
+ "OpMemberDecorate %Point 0 Offset 8\n"
+ "OpMemberDecorate %Point 2 NoPerspective\n"
+ "OpMemberDecorate %Point 2 Offset 16\n"},
+ }), );
+
+INSTANTIATE_TEST_CASE_P(UnrelatedDecorations, FlattenDecorationTest,
+ ::testing::ValuesIn(std::vector<FlattenDecorationCase>{
+ // A non-group non-member decoration is untouched.
+ {"OpDecorate %hue Centroid\n"
+ "OpDecorate %saturation Flat\n",
+ "OpDecorate %hue Centroid\n"
+ "OpDecorate %saturation Flat\n"},
+ // A non-group member decoration is untouched.
+ {"OpMemberDecorate %Point 0 Offset 0\n"
+ "OpMemberDecorate %Point 1 Offset 4\n"
+ "OpMemberDecorate %Point 1 Flat\n",
+ "OpMemberDecorate %Point 0 Offset 0\n"
+ "OpMemberDecorate %Point 1 Offset 4\n"
+ "OpMemberDecorate %Point 1 Flat\n"},
+ // A non-group non-member decoration survives any
+ // replacement of group decorations.
+ {"OpName %group \"group\"\n"
+ "OpDecorate %group Flat\n"
+ "OpDecorate %hue Centroid\n"
+ "OpDecorate %group NoPerspective\n"
+ "%group = OpDecorationGroup\n"
+ "OpGroupDecorate %group %hue %saturation\n",
+ "OpDecorate %hue Flat\n"
+ "OpDecorate %saturation Flat\n"
+ "OpDecorate %hue Centroid\n"
+ "OpDecorate %hue NoPerspective\n"
+ "OpDecorate %saturation NoPerspective\n"},
+ // A non-group member decoration survives any
+ // replacement of group decorations.
+ {"OpDecorate %group Offset 0\n"
+ "OpDecorate %group Flat\n"
+ "OpMemberDecorate %Point 1 Offset 4\n"
+ "%group = OpDecorationGroup\n"
+ "OpGroupMemberDecorate %group %Point 0\n",
+ "OpMemberDecorate %Point 0 Offset 0\n"
+ "OpMemberDecorate %Point 0 Flat\n"
+ "OpMemberDecorate %Point 1 Offset 4\n"},
+ }), );
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/fold_spec_const_op_composite_test.cpp b/test/opt/fold_spec_const_op_composite_test.cpp
new file mode 100644
index 0000000..8ecfd5c
--- /dev/null
+++ b/test/opt/fold_spec_const_op_composite_test.cpp
@@ -0,0 +1,1394 @@
+// Copyright (c) 2016 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 <sstream>
+#include <string>
+#include <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FoldSpecConstantOpAndCompositePassBasicTest = PassTest<::testing::Test>;
+
+TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, Empty) {
+ SinglePassRunAndCheck<FoldSpecConstantOpAndCompositePass>(
+ "", "", /* skip_nop = */ true);
+}
+
+// A test of the basic functionality of FoldSpecConstantOpAndCompositePass.
+// A spec constant defined with an integer addition operation should be folded
+// to a normal constant with fixed value.
+TEST_F(FoldSpecConstantOpAndCompositePassBasicTest, Basic) {
+ AssemblyBuilder builder;
+ builder.AppendTypesConstantsGlobals({
+ // clang-format off
+ "%int = OpTypeInt 32 1",
+ "%frozen_spec_const_int = OpConstant %int 1",
+ "%const_int = OpConstant %int 2",
+ // Folding target:
+ "%spec_add = OpSpecConstantOp %int IAdd %frozen_spec_const_int %const_int",
+ // clang-format on
+ });
+
+ std::vector<const char*> expected = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %void \"void\"",
+ "OpName %main_func_type \"main_func_type\"",
+ "OpName %main \"main\"",
+ "OpName %main_func_entry_block \"main_func_entry_block\"",
+ "OpName %int \"int\"",
+ "OpName %frozen_spec_const_int \"frozen_spec_const_int\"",
+ "OpName %const_int \"const_int\"",
+ "OpName %spec_add \"spec_add\"",
+ "%void = OpTypeVoid",
+ "%main_func_type = OpTypeFunction %void",
+ "%int = OpTypeInt 32 1",
+"%frozen_spec_const_int = OpConstant %int 1",
+ "%const_int = OpConstant %int 2",
+ // The SpecConstantOp IAdd instruction should be replace by OpConstant
+ // instruction:
+ "%spec_add = OpConstant %int 3",
+ "%main = OpFunction %void None %main_func_type",
+"%main_func_entry_block = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<FoldSpecConstantOpAndCompositePass>(
+ builder.GetCode(), JoinAllInsts(expected), /* skip_nop = */ true);
+}
+
+// A test of skipping folding an instruction when the instruction result type
+// has decorations.
+TEST_F(FoldSpecConstantOpAndCompositePassBasicTest,
+ SkipWhenTypeHasDecorations) {
+ AssemblyBuilder builder;
+ builder
+ .AppendAnnotations({
+ // clang-format off
+ "OpDecorate %int RelaxedPrecision",
+ // clang-format on
+ })
+ .AppendTypesConstantsGlobals({
+ // clang-format off
+ "%int = OpTypeInt 32 1",
+ "%frozen_spec_const_int = OpConstant %int 1",
+ "%const_int = OpConstant %int 2",
+ // The following spec constant should not be folded as the result type
+ // has relaxed precision decoration.
+ "%spec_add = OpSpecConstantOp %int IAdd %frozen_spec_const_int %const_int",
+ // clang-format on
+ });
+
+ SinglePassRunAndCheck<FoldSpecConstantOpAndCompositePass>(
+ builder.GetCode(), builder.GetCode(), /* skip_nop = */ true);
+}
+
+// All types and some common constants that are potentially required in
+// FoldSpecConstantOpAndCompositeTest.
+std::vector<std::string> CommonTypesAndConstants() {
+ return std::vector<std::string>{
+ // clang-format off
+ // scalar types
+ "%bool = OpTypeBool",
+ "%uint = OpTypeInt 32 0",
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%double = OpTypeFloat 64",
+ // vector types
+ "%v2bool = OpTypeVector %bool 2",
+ "%v2uint = OpTypeVector %uint 2",
+ "%v2int = OpTypeVector %int 2",
+ "%v3int = OpTypeVector %int 3",
+ "%v4int = OpTypeVector %int 4",
+ "%v2float = OpTypeVector %float 2",
+ "%v2double = OpTypeVector %double 2",
+ // variable pointer types
+ "%_pf_bool = OpTypePointer Function %bool",
+ "%_pf_uint = OpTypePointer Function %uint",
+ "%_pf_int = OpTypePointer Function %int",
+ "%_pf_float = OpTypePointer Function %float",
+ "%_pf_double = OpTypePointer Function %double",
+ "%_pf_v2int = OpTypePointer Function %v2int",
+ "%_pf_v2float = OpTypePointer Function %v2float",
+ "%_pf_v2double = OpTypePointer Function %v2double",
+ // struct types
+ "%inner_struct = OpTypeStruct %bool %int %float",
+ "%outer_struct = OpTypeStruct %inner_struct %int",
+ "%flat_struct = OpTypeStruct %bool %int %float",
+
+ // common constants
+ // scalar constants:
+ "%bool_true = OpConstantTrue %bool",
+ "%bool_false = OpConstantFalse %bool",
+ "%bool_null = OpConstantNull %bool",
+ "%signed_zero = OpConstant %int 0",
+ "%unsigned_zero = OpConstant %uint 0",
+ "%signed_one = OpConstant %int 1",
+ "%unsigned_one = OpConstant %uint 1",
+ "%signed_two = OpConstant %int 2",
+ "%unsigned_two = OpConstant %uint 2",
+ "%signed_three = OpConstant %int 3",
+ "%unsigned_three = OpConstant %uint 3",
+ "%signed_null = OpConstantNull %int",
+ "%unsigned_null = OpConstantNull %uint",
+ // vector constants:
+ "%bool_true_vec = OpConstantComposite %v2bool %bool_true %bool_true",
+ "%bool_false_vec = OpConstantComposite %v2bool %bool_false %bool_false",
+ "%bool_null_vec = OpConstantNull %v2bool",
+ "%signed_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero",
+ "%unsigned_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+ "%signed_one_vec = OpConstantComposite %v2int %signed_one %signed_one",
+ "%unsigned_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+ "%signed_two_vec = OpConstantComposite %v2int %signed_two %signed_two",
+ "%unsigned_two_vec = OpConstantComposite %v2uint %unsigned_two %unsigned_two",
+ "%signed_three_vec = OpConstantComposite %v2int %signed_three %signed_three",
+ "%unsigned_three_vec = OpConstantComposite %v2uint %unsigned_three %unsigned_three",
+ "%signed_null_vec = OpConstantNull %v2int",
+ "%unsigned_null_vec = OpConstantNull %v2uint",
+ "%v4int_0_1_2_3 = OpConstantComposite %v4int %signed_zero %signed_one %signed_two %signed_three",
+ // clang-format on
+ };
+}
+
+// A helper function to strip OpName instructions from the given string of
+// disassembly code. Returns the string with all OpName instruction stripped.
+std::string StripOpNameInstructions(const std::string& str) {
+ std::stringstream ss(str);
+ std::ostringstream oss;
+ std::string inst_str;
+ while (std::getline(ss, inst_str, '\n')) {
+ if (inst_str.find("OpName %") == std::string::npos) {
+ oss << inst_str << '\n';
+ }
+ }
+ return oss.str();
+}
+
+struct FoldSpecConstantOpAndCompositePassTestCase {
+ // Original constants with unfolded spec constants.
+ std::vector<std::string> original;
+ // Expected cosntants after folding.
+ std::vector<std::string> expected;
+};
+
+using FoldSpecConstantOpAndCompositePassTest = PassTest<
+ ::testing::TestWithParam<FoldSpecConstantOpAndCompositePassTestCase>>;
+
+TEST_P(FoldSpecConstantOpAndCompositePassTest, ParamTestCase) {
+ AssemblyBuilder test_code_builder, expected_code_builder;
+ const auto& tc = GetParam();
+ test_code_builder.AppendTypesConstantsGlobals(CommonTypesAndConstants());
+ test_code_builder.AppendTypesConstantsGlobals(tc.original);
+ expected_code_builder.AppendTypesConstantsGlobals(CommonTypesAndConstants());
+ expected_code_builder.AppendTypesConstantsGlobals(tc.expected);
+ const std::string original = test_code_builder.GetCode();
+ const std::string expected = expected_code_builder.GetCode();
+
+ // Run the optimization and get the result code in disassembly.
+ std::string optimized;
+ auto status = Pass::Status::SuccessWithoutChange;
+ std::tie(optimized, status) =
+ SinglePassRunAndDisassemble<FoldSpecConstantOpAndCompositePass>(
+ original, /* skip_nop = */ true, /* do_validation = */ false);
+
+ // Check the optimized code, but ignore the OpName instructions.
+ EXPECT_NE(Pass::Status::Failure, status);
+ EXPECT_EQ(
+ StripOpNameInstructions(expected) == StripOpNameInstructions(original),
+ status == Pass::Status::SuccessWithoutChange);
+ EXPECT_EQ(StripOpNameInstructions(expected),
+ StripOpNameInstructions(optimized));
+}
+
+// Tests that OpSpecConstantComposite opcodes are replace with
+// OpConstantComposite correctly.
+INSTANTIATE_TEST_CASE_P(
+ Composite, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(std::vector<
+ FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // normal vector
+ {
+ // original
+ {
+ "%spec_v2bool = OpSpecConstantComposite %v2bool %bool_true %bool_false",
+ "%spec_v2uint = OpSpecConstantComposite %v2uint %unsigned_one %unsigned_one",
+ "%spec_v2int_a = OpSpecConstantComposite %v2int %signed_one %signed_two",
+ // Spec constants whose value can not be fully resolved should
+ // not be processed.
+ "%spec_int = OpSpecConstant %int 99",
+ "%spec_v2int_b = OpSpecConstantComposite %v2int %signed_one %spec_int",
+ },
+ // expected
+ {
+ "%spec_v2bool = OpConstantComposite %v2bool %bool_true %bool_false",
+ "%spec_v2uint = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+ "%spec_v2int_a = OpConstantComposite %v2int %signed_one %signed_two",
+ "%spec_int = OpSpecConstant %int 99",
+ "%spec_v2int_b = OpSpecConstantComposite %v2int %signed_one %spec_int",
+ },
+ },
+ // vector with null constants
+ {
+ // original
+ {
+ "%null_bool = OpConstantNull %bool",
+ "%null_int = OpConstantNull %int",
+ "%spec_v2bool = OpSpecConstantComposite %v2bool %null_bool %null_bool",
+ "%spec_v3int = OpSpecConstantComposite %v3int %null_int %null_int %null_int",
+ "%spec_v4int = OpSpecConstantComposite %v4int %null_int %null_int %null_int %null_int",
+ },
+ // expected
+ {
+ "%null_bool = OpConstantNull %bool",
+ "%null_int = OpConstantNull %int",
+ "%spec_v2bool = OpConstantComposite %v2bool %null_bool %null_bool",
+ "%spec_v3int = OpConstantComposite %v3int %null_int %null_int %null_int",
+ "%spec_v4int = OpConstantComposite %v4int %null_int %null_int %null_int %null_int",
+ },
+ },
+ // flat struct
+ {
+ // original
+ {
+ "%float_1 = OpConstant %float 1",
+ "%flat_1 = OpSpecConstantComposite %flat_struct %bool_true %signed_null %float_1",
+ // following struct should not be folded as the value of
+ // %spec_float is not determined.
+ "%spec_float = OpSpecConstant %float 1",
+ "%flat_2 = OpSpecConstantComposite %flat_struct %bool_true %signed_one %spec_float",
+ },
+ // expected
+ {
+ "%float_1 = OpConstant %float 1",
+ "%flat_1 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1",
+ "%spec_float = OpSpecConstant %float 1",
+ "%flat_2 = OpSpecConstantComposite %flat_struct %bool_true %signed_one %spec_float",
+ }
+ },
+ // nested struct
+ {
+ // original
+ {
+ "%float_1 = OpConstant %float 1",
+ "%inner_1 = OpSpecConstantComposite %inner_struct %bool_true %signed_null %float_1",
+ "%outer_1 = OpSpecConstantComposite %outer_struct %inner_1 %signed_one",
+ // following structs should not be folded as the value of
+ // %spec_float is not determined.
+ "%spec_float = OpSpecConstant %float 1",
+ "%inner_2 = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float",
+ "%outer_2 = OpSpecConstantComposite %outer_struct %inner_2 %signed_one",
+ },
+ // expected
+ {
+ "%float_1 = OpConstant %float 1",
+ "%inner_1 = OpConstantComposite %inner_struct %bool_true %signed_null %float_1",
+ "%outer_1 = OpConstantComposite %outer_struct %inner_1 %signed_one",
+ "%spec_float = OpSpecConstant %float 1",
+ "%inner_2 = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float",
+ "%outer_2 = OpSpecConstantComposite %outer_struct %inner_2 %signed_one",
+ }
+ },
+ // composite constants touched by OpUndef should be skipped
+ {
+ // original
+ {
+ "%undef = OpUndef %float",
+ "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef",
+ "%outer = OpSpecConstantComposite %outer_struct %inner %signed_one",
+ },
+ // expected
+ {
+ "%undef = OpUndef %float",
+ "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef",
+ "%outer = OpSpecConstantComposite %outer_struct %inner %signed_one",
+ },
+ }
+ // clang-format on
+ })));
+
+// Tests for operations that resulting in different types.
+INSTANTIATE_TEST_CASE_P(
+ Cast, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(
+ std::vector<FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // int -> bool scalar
+ {
+ // original
+ {
+ "%spec_bool_t = OpSpecConstantOp %bool INotEqual %signed_three %signed_zero",
+ "%spec_bool_f = OpSpecConstantOp %bool INotEqual %signed_zero %signed_zero",
+ "%spec_bool_from_null = OpSpecConstantOp %bool INotEqual %signed_null %signed_zero",
+ },
+ // expected
+ {
+ "%spec_bool_t = OpConstantTrue %bool",
+ "%spec_bool_f = OpConstantFalse %bool",
+ "%spec_bool_from_null = OpConstantFalse %bool",
+ },
+ },
+
+ // uint -> bool scalar
+ {
+ // original
+ {
+ "%spec_bool_t = OpSpecConstantOp %bool INotEqual %unsigned_three %unsigned_zero",
+ "%spec_bool_f = OpSpecConstantOp %bool INotEqual %unsigned_zero %unsigned_zero",
+ "%spec_bool_from_null = OpSpecConstantOp %bool INotEqual %unsigned_null %unsigned_zero",
+ },
+ // expected
+ {
+ "%spec_bool_t = OpConstantTrue %bool",
+ "%spec_bool_f = OpConstantFalse %bool",
+ "%spec_bool_from_null = OpConstantFalse %bool",
+ },
+ },
+
+ // bool -> int scalar
+ {
+ // original
+ {
+ "%spec_int_one = OpSpecConstantOp %int Select %bool_true %signed_one %signed_zero",
+ "%spec_int_zero = OpSpecConstantOp %int Select %bool_false %signed_one %signed_zero",
+ "%spec_int_from_null = OpSpecConstantOp %int Select %bool_null %signed_one %signed_zero",
+ },
+ // expected
+ {
+ "%spec_int_one = OpConstant %int 1",
+ "%spec_int_zero = OpConstant %int 0",
+ "%spec_int_from_null = OpConstant %int 0",
+ },
+ },
+
+ // uint -> int scalar
+ {
+ // original
+ {
+ "%spec_int_one = OpSpecConstantOp %int IAdd %unsigned_one %signed_zero",
+ "%spec_int_zero = OpSpecConstantOp %int IAdd %unsigned_zero %signed_zero",
+ "%spec_int_from_null = OpSpecConstantOp %int IAdd %unsigned_null %unsigned_zero",
+ },
+ // expected
+ {
+ "%spec_int_one = OpConstant %int 1",
+ "%spec_int_zero = OpConstant %int 0",
+ "%spec_int_from_null = OpConstant %int 0",
+ },
+ },
+
+ // bool -> uint scalar
+ {
+ // original
+ {
+ "%spec_uint_one = OpSpecConstantOp %uint Select %bool_true %unsigned_one %unsigned_zero",
+ "%spec_uint_zero = OpSpecConstantOp %uint Select %bool_false %unsigned_one %unsigned_zero",
+ "%spec_uint_from_null = OpSpecConstantOp %uint Select %bool_null %unsigned_one %unsigned_zero",
+ },
+ // expected
+ {
+ "%spec_uint_one = OpConstant %uint 1",
+ "%spec_uint_zero = OpConstant %uint 0",
+ "%spec_uint_from_null = OpConstant %uint 0",
+ },
+ },
+
+ // int -> uint scalar
+ {
+ // original
+ {
+ "%spec_uint_one = OpSpecConstantOp %uint IAdd %signed_one %unsigned_zero",
+ "%spec_uint_zero = OpSpecConstantOp %uint IAdd %signed_zero %unsigned_zero",
+ "%spec_uint_from_null = OpSpecConstantOp %uint IAdd %signed_null %unsigned_zero",
+ },
+ // expected
+ {
+ "%spec_uint_one = OpConstant %uint 1",
+ "%spec_uint_zero = OpConstant %uint 0",
+ "%spec_uint_from_null = OpConstant %uint 0",
+ },
+ },
+
+ // int -> bool vector
+ {
+ // original
+ {
+ "%spec_bool_t_vec = OpSpecConstantOp %v2bool INotEqual %signed_three_vec %signed_zero_vec",
+ "%spec_bool_f_vec = OpSpecConstantOp %v2bool INotEqual %signed_zero_vec %signed_zero_vec",
+ "%spec_bool_from_null = OpSpecConstantOp %v2bool INotEqual %signed_null_vec %signed_zero_vec",
+ },
+ // expected
+ {
+ "%true = OpConstantTrue %bool",
+ "%true_0 = OpConstantTrue %bool",
+ "%spec_bool_t_vec = OpConstantComposite %v2bool %bool_true %bool_true",
+ "%false = OpConstantFalse %bool",
+ "%false_0 = OpConstantFalse %bool",
+ "%spec_bool_f_vec = OpConstantComposite %v2bool %bool_false %bool_false",
+ "%false_1 = OpConstantFalse %bool",
+ "%false_2 = OpConstantFalse %bool",
+ "%spec_bool_from_null = OpConstantComposite %v2bool %bool_false %bool_false",
+ },
+ },
+
+ // uint -> bool vector
+ {
+ // original
+ {
+ "%spec_bool_t_vec = OpSpecConstantOp %v2bool INotEqual %unsigned_three_vec %unsigned_zero_vec",
+ "%spec_bool_f_vec = OpSpecConstantOp %v2bool INotEqual %unsigned_zero_vec %unsigned_zero_vec",
+ "%spec_bool_from_null = OpSpecConstantOp %v2bool INotEqual %unsigned_null_vec %unsigned_zero_vec",
+ },
+ // expected
+ {
+ "%true = OpConstantTrue %bool",
+ "%true_0 = OpConstantTrue %bool",
+ "%spec_bool_t_vec = OpConstantComposite %v2bool %bool_true %bool_true",
+ "%false = OpConstantFalse %bool",
+ "%false_0 = OpConstantFalse %bool",
+ "%spec_bool_f_vec = OpConstantComposite %v2bool %bool_false %bool_false",
+ "%false_1 = OpConstantFalse %bool",
+ "%false_2 = OpConstantFalse %bool",
+ "%spec_bool_from_null = OpConstantComposite %v2bool %bool_false %bool_false",
+ },
+ },
+
+ // bool -> int vector
+ {
+ // original
+ {
+ "%spec_int_one_vec = OpSpecConstantOp %v2int Select %bool_true_vec %signed_one_vec %signed_zero_vec",
+ "%spec_int_zero_vec = OpSpecConstantOp %v2int Select %bool_false_vec %signed_one_vec %signed_zero_vec",
+ "%spec_int_from_null = OpSpecConstantOp %v2int Select %bool_null_vec %signed_one_vec %signed_zero_vec",
+ },
+ // expected
+ {
+ "%int_1 = OpConstant %int 1",
+ "%int_1_0 = OpConstant %int 1",
+ "%spec_int_one_vec = OpConstantComposite %v2int %signed_one %signed_one",
+ "%int_0 = OpConstant %int 0",
+ "%int_0_0 = OpConstant %int 0",
+ "%spec_int_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero",
+ "%int_0_1 = OpConstant %int 0",
+ "%int_0_2 = OpConstant %int 0",
+ "%spec_int_from_null = OpConstantComposite %v2int %signed_zero %signed_zero",
+ },
+ },
+
+ // uint -> int vector
+ {
+ // original
+ {
+ "%spec_int_one_vec = OpSpecConstantOp %v2int IAdd %unsigned_one_vec %signed_zero_vec",
+ "%spec_int_zero_vec = OpSpecConstantOp %v2int IAdd %unsigned_zero_vec %signed_zero_vec",
+ "%spec_int_from_null = OpSpecConstantOp %v2int IAdd %unsigned_null_vec %signed_zero_vec",
+ },
+ // expected
+ {
+ "%int_1 = OpConstant %int 1",
+ "%int_1_0 = OpConstant %int 1",
+ "%spec_int_one_vec = OpConstantComposite %v2int %signed_one %signed_one",
+ "%int_0 = OpConstant %int 0",
+ "%int_0_0 = OpConstant %int 0",
+ "%spec_int_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero",
+ "%int_0_1 = OpConstant %int 0",
+ "%int_0_2 = OpConstant %int 0",
+ "%spec_int_from_null = OpConstantComposite %v2int %signed_zero %signed_zero",
+ },
+ },
+
+ // bool -> uint vector
+ {
+ // original
+ {
+ "%spec_uint_one_vec = OpSpecConstantOp %v2uint Select %bool_true_vec %unsigned_one_vec %unsigned_zero_vec",
+ "%spec_uint_zero_vec = OpSpecConstantOp %v2uint Select %bool_false_vec %unsigned_one_vec %unsigned_zero_vec",
+ "%spec_uint_from_null = OpSpecConstantOp %v2uint Select %bool_null_vec %unsigned_one_vec %unsigned_zero_vec",
+ },
+ // expected
+ {
+ "%uint_1 = OpConstant %uint 1",
+ "%uint_1_0 = OpConstant %uint 1",
+ "%spec_uint_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+ "%uint_0 = OpConstant %uint 0",
+ "%uint_0_0 = OpConstant %uint 0",
+ "%spec_uint_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+ "%uint_0_1 = OpConstant %uint 0",
+ "%uint_0_2 = OpConstant %uint 0",
+ "%spec_uint_from_null = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+ },
+ },
+
+ // int -> uint vector
+ {
+ // original
+ {
+ "%spec_uint_one_vec = OpSpecConstantOp %v2uint IAdd %signed_one_vec %unsigned_zero_vec",
+ "%spec_uint_zero_vec = OpSpecConstantOp %v2uint IAdd %signed_zero_vec %unsigned_zero_vec",
+ "%spec_uint_from_null = OpSpecConstantOp %v2uint IAdd %signed_null_vec %unsigned_zero_vec",
+ },
+ // expected
+ {
+ "%uint_1 = OpConstant %uint 1",
+ "%uint_1_0 = OpConstant %uint 1",
+ "%spec_uint_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+ "%uint_0 = OpConstant %uint 0",
+ "%uint_0_0 = OpConstant %uint 0",
+ "%spec_uint_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+ "%uint_0_1 = OpConstant %uint 0",
+ "%uint_0_2 = OpConstant %uint 0",
+ "%spec_uint_from_null = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+ },
+ },
+ // clang-format on
+ })));
+
+// Tests about boolean scalar logical operations and comparison operations with
+// scalar int/uint type.
+INSTANTIATE_TEST_CASE_P(
+ Logical, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(std::vector<
+ FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // scalar integer comparison
+ {
+ // original
+ {
+ "%int_minus_1 = OpConstant %int -1",
+
+ "%slt_0_1 = OpSpecConstantOp %bool SLessThan %signed_zero %signed_one",
+ "%sgt_0_1 = OpSpecConstantOp %bool SGreaterThan %signed_zero %signed_one",
+ "%sle_2_2 = OpSpecConstantOp %bool SLessThanEqual %signed_two %signed_two",
+ "%sge_2_1 = OpSpecConstantOp %bool SGreaterThanEqual %signed_two %signed_one",
+ "%sge_2_null = OpSpecConstantOp %bool SGreaterThanEqual %signed_two %signed_null",
+ "%sge_minus_1_null = OpSpecConstantOp %bool SGreaterThanEqual %int_minus_1 %signed_null",
+
+ "%ult_0_1 = OpSpecConstantOp %bool ULessThan %unsigned_zero %unsigned_one",
+ "%ugt_0_1 = OpSpecConstantOp %bool UGreaterThan %unsigned_zero %unsigned_one",
+ "%ule_2_3 = OpSpecConstantOp %bool ULessThanEqual %unsigned_two %unsigned_three",
+ "%uge_1_1 = OpSpecConstantOp %bool UGreaterThanEqual %unsigned_one %unsigned_one",
+ "%uge_2_null = OpSpecConstantOp %bool UGreaterThanEqual %unsigned_two %unsigned_null",
+ "%uge_minus_1_null = OpSpecConstantOp %bool UGreaterThanEqual %int_minus_1 %unsigned_null",
+ },
+ // expected
+ {
+ "%int_minus_1 = OpConstant %int -1",
+
+ "%slt_0_1 = OpConstantTrue %bool",
+ "%sgt_0_1 = OpConstantFalse %bool",
+ "%sle_2_2 = OpConstantTrue %bool",
+ "%sge_2_1 = OpConstantTrue %bool",
+ "%sge_2_null = OpConstantTrue %bool",
+ "%sge_minus_1_null = OpConstantFalse %bool",
+
+ "%ult_0_1 = OpConstantTrue %bool",
+ "%ugt_0_1 = OpConstantFalse %bool",
+ "%ule_2_3 = OpConstantTrue %bool",
+ "%uge_1_1 = OpConstantTrue %bool",
+ "%uge_2_null = OpConstantTrue %bool",
+ "%uge_minus_1_null = OpConstantTrue %bool",
+ },
+ },
+ // Logical and, or, xor.
+ {
+ // original
+ {
+ "%logical_or = OpSpecConstantOp %bool LogicalOr %bool_true %bool_false",
+ "%logical_and = OpSpecConstantOp %bool LogicalAnd %bool_true %bool_false",
+ "%logical_not = OpSpecConstantOp %bool LogicalNot %bool_true",
+ "%logical_eq = OpSpecConstantOp %bool LogicalEqual %bool_true %bool_true",
+ "%logical_neq = OpSpecConstantOp %bool LogicalNotEqual %bool_true %bool_true",
+ "%logical_and_null = OpSpecConstantOp %bool LogicalAnd %bool_true %bool_null",
+ },
+ // expected
+ {
+ "%logical_or = OpConstantTrue %bool",
+ "%logical_and = OpConstantFalse %bool",
+ "%logical_not = OpConstantFalse %bool",
+ "%logical_eq = OpConstantTrue %bool",
+ "%logical_neq = OpConstantFalse %bool",
+ "%logical_and_null = OpConstantFalse %bool",
+ },
+ },
+ // clang-format on
+ })));
+
+// Tests about arithmetic operations for scalar int and uint types.
+INSTANTIATE_TEST_CASE_P(
+ ScalarArithmetic, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(std::vector<
+ FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // scalar integer negate
+ {
+ // original
+ {
+ "%int_minus_1 = OpSpecConstantOp %int SNegate %signed_one",
+ "%int_minus_2 = OpSpecConstantOp %int SNegate %signed_two",
+ "%int_neg_null = OpSpecConstantOp %int SNegate %signed_null",
+ "%int_max = OpConstant %int 2147483647",
+ "%int_neg_max = OpSpecConstantOp %int SNegate %int_max",
+ },
+ // expected
+ {
+ "%int_minus_1 = OpConstant %int -1",
+ "%int_minus_2 = OpConstant %int -2",
+ "%int_neg_null = OpConstant %int 0",
+ "%int_max = OpConstant %int 2147483647",
+ "%int_neg_max = OpConstant %int -2147483647",
+ },
+ },
+ // scalar integer not
+ {
+ // original
+ {
+ "%uint_4294967294 = OpSpecConstantOp %uint Not %unsigned_one",
+ "%uint_4294967293 = OpSpecConstantOp %uint Not %unsigned_two",
+ "%uint_neg_null = OpSpecConstantOp %uint Not %unsigned_null",
+ },
+ // expected
+ {
+ "%uint_4294967294 = OpConstant %uint 4294967294",
+ "%uint_4294967293 = OpConstant %uint 4294967293",
+ "%uint_neg_null = OpConstant %uint 4294967295",
+ },
+ },
+ // scalar integer add, sub, mul, div
+ {
+ // original
+ {
+ "%signed_max = OpConstant %int 2147483647",
+ "%signed_min = OpConstant %int -2147483648",
+
+ "%spec_int_iadd = OpSpecConstantOp %int IAdd %signed_three %signed_two",
+ "%spec_int_isub = OpSpecConstantOp %int ISub %signed_one %spec_int_iadd",
+ "%spec_int_sdiv = OpSpecConstantOp %int SDiv %spec_int_isub %signed_two",
+ "%spec_int_imul = OpSpecConstantOp %int IMul %spec_int_sdiv %signed_three",
+ "%spec_int_iadd_null = OpSpecConstantOp %int IAdd %spec_int_imul %signed_null",
+ "%spec_int_imul_null = OpSpecConstantOp %int IMul %spec_int_iadd_null %signed_null",
+ "%spec_int_iadd_overflow = OpSpecConstantOp %int IAdd %signed_max %signed_three",
+ "%spec_int_isub_overflow = OpSpecConstantOp %int ISub %signed_min %signed_three",
+
+ "%spec_uint_iadd = OpSpecConstantOp %uint IAdd %unsigned_three %unsigned_two",
+ "%spec_uint_isub = OpSpecConstantOp %uint ISub %unsigned_one %spec_uint_iadd",
+ "%spec_uint_udiv = OpSpecConstantOp %uint UDiv %spec_uint_isub %unsigned_three",
+ "%spec_uint_imul = OpSpecConstantOp %uint IMul %spec_uint_udiv %unsigned_two",
+ "%spec_uint_isub_null = OpSpecConstantOp %uint ISub %spec_uint_imul %signed_null",
+ },
+ // expected
+ {
+ "%signed_max = OpConstant %int 2147483647",
+ "%signed_min = OpConstant %int -2147483648",
+
+ "%spec_int_iadd = OpConstant %int 5",
+ "%spec_int_isub = OpConstant %int -4",
+ "%spec_int_sdiv = OpConstant %int -2",
+ "%spec_int_imul = OpConstant %int -6",
+ "%spec_int_iadd_null = OpConstant %int -6",
+ "%spec_int_imul_null = OpConstant %int 0",
+ "%spec_int_iadd_overflow = OpConstant %int -2147483646",
+ "%spec_int_isub_overflow = OpConstant %int 2147483645",
+
+ "%spec_uint_iadd = OpConstant %uint 5",
+ "%spec_uint_isub = OpConstant %uint 4294967292",
+ "%spec_uint_udiv = OpConstant %uint 1431655764",
+ "%spec_uint_imul = OpConstant %uint 2863311528",
+ "%spec_uint_isub_null = OpConstant %uint 2863311528",
+ },
+ },
+ // scalar integer rem, mod
+ {
+ // original
+ {
+ // common constants
+ "%int_7 = OpConstant %int 7",
+ "%uint_7 = OpConstant %uint 7",
+ "%int_minus_7 = OpConstant %int -7",
+ "%int_minus_3 = OpConstant %int -3",
+
+ // srem
+ "%7_srem_3 = OpSpecConstantOp %int SRem %int_7 %signed_three",
+ "%minus_7_srem_3 = OpSpecConstantOp %int SRem %int_minus_7 %signed_three",
+ "%7_srem_minus_3 = OpSpecConstantOp %int SRem %int_7 %int_minus_3",
+ "%minus_7_srem_minus_3 = OpSpecConstantOp %int SRem %int_minus_7 %int_minus_3",
+ // smod
+ "%7_smod_3 = OpSpecConstantOp %int SMod %int_7 %signed_three",
+ "%minus_7_smod_3 = OpSpecConstantOp %int SMod %int_minus_7 %signed_three",
+ "%7_smod_minus_3 = OpSpecConstantOp %int SMod %int_7 %int_minus_3",
+ "%minus_7_smod_minus_3 = OpSpecConstantOp %int SMod %int_minus_7 %int_minus_3",
+ // umod
+ "%7_umod_3 = OpSpecConstantOp %uint UMod %uint_7 %unsigned_three",
+ // null constant
+ "%null_srem_3 = OpSpecConstantOp %int SRem %signed_null %signed_three",
+ "%null_smod_3 = OpSpecConstantOp %int SMod %signed_null %signed_three",
+ "%null_umod_3 = OpSpecConstantOp %uint UMod %unsigned_null %unsigned_three",
+ },
+ // expected
+ {
+ // common constants
+ "%int_7 = OpConstant %int 7",
+ "%uint_7 = OpConstant %uint 7",
+ "%int_minus_7 = OpConstant %int -7",
+ "%int_minus_3 = OpConstant %int -3",
+
+ // srem
+ "%7_srem_3 = OpConstant %int 1",
+ "%minus_7_srem_3 = OpConstant %int -1",
+ "%7_srem_minus_3 = OpConstant %int 1",
+ "%minus_7_srem_minus_3 = OpConstant %int -1",
+ // smod
+ "%7_smod_3 = OpConstant %int 1",
+ "%minus_7_smod_3 = OpConstant %int 2",
+ "%7_smod_minus_3 = OpConstant %int -2",
+ "%minus_7_smod_minus_3 = OpConstant %int -1",
+ // umod
+ "%7_umod_3 = OpConstant %uint 1",
+ // null constant
+ "%null_srem_3 = OpConstant %int 0",
+ "%null_smod_3 = OpConstant %int 0",
+ "%null_umod_3 = OpConstant %uint 0",
+ },
+ },
+ // scalar integer bitwise and shift
+ {
+ // original
+ {
+ // bitwise
+ "%xor_1_3 = OpSpecConstantOp %int BitwiseXor %signed_one %signed_three",
+ "%and_1_2 = OpSpecConstantOp %int BitwiseAnd %signed_one %xor_1_3",
+ "%or_1_2 = OpSpecConstantOp %int BitwiseOr %signed_one %xor_1_3",
+ "%xor_3_null = OpSpecConstantOp %int BitwiseXor %or_1_2 %signed_null",
+
+ // shift
+ "%unsigned_31 = OpConstant %uint 31",
+ "%unsigned_left_shift_max = OpSpecConstantOp %uint ShiftLeftLogical %unsigned_one %unsigned_31",
+ "%unsigned_right_shift_logical = OpSpecConstantOp %uint ShiftRightLogical %unsigned_left_shift_max %unsigned_31",
+ "%signed_right_shift_arithmetic = OpSpecConstantOp %int ShiftRightArithmetic %unsigned_left_shift_max %unsigned_31",
+ "%left_shift_null_31 = OpSpecConstantOp %uint ShiftLeftLogical %unsigned_null %unsigned_31",
+ "%right_shift_31_null = OpSpecConstantOp %uint ShiftRightLogical %unsigned_31 %unsigned_null",
+ },
+ // expected
+ {
+ "%xor_1_3 = OpConstant %int 2",
+ "%and_1_2 = OpConstant %int 0",
+ "%or_1_2 = OpConstant %int 3",
+ "%xor_3_null = OpConstant %int 3",
+
+ "%unsigned_31 = OpConstant %uint 31",
+ "%unsigned_left_shift_max = OpConstant %uint 2147483648",
+ "%unsigned_right_shift_logical = OpConstant %uint 1",
+ "%signed_right_shift_arithmetic = OpConstant %int -1",
+ "%left_shift_null_31 = OpConstant %uint 0",
+ "%right_shift_31_null = OpConstant %uint 31",
+ },
+ },
+ // Skip folding if any operands have undetermined value.
+ {
+ // original
+ {
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_iadd = OpSpecConstantOp %int IAdd %signed_three %spec_int",
+ },
+ // expected
+ {
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_iadd = OpSpecConstantOp %int IAdd %signed_three %spec_int",
+ },
+ },
+ // clang-format on
+ })));
+
+// Tests about arithmetic operations for vector int and uint types.
+INSTANTIATE_TEST_CASE_P(
+ VectorArithmetic, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(std::vector<
+ FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // vector integer negate
+ {
+ // original
+ {
+ "%v2int_minus_1 = OpSpecConstantOp %v2int SNegate %signed_one_vec",
+ "%v2int_minus_2 = OpSpecConstantOp %v2int SNegate %signed_two_vec",
+ "%v2int_neg_null = OpSpecConstantOp %v2int SNegate %signed_null_vec",
+ },
+ // expected
+ {
+ "%int_n1 = OpConstant %int -1",
+ "%int_n1_0 = OpConstant %int -1",
+ "%v2int_minus_1 = OpConstantComposite %v2int %int_n1 %int_n1",
+ "%int_n2 = OpConstant %int -2",
+ "%int_n2_0 = OpConstant %int -2",
+ "%v2int_minus_2 = OpConstantComposite %v2int %int_n2 %int_n2",
+ "%int_0 = OpConstant %int 0",
+ "%int_0_0 = OpConstant %int 0",
+ "%v2int_neg_null = OpConstantComposite %v2int %signed_zero %signed_zero",
+ },
+ },
+ // vector integer (including null vetors) add, sub, div, mul
+ {
+ // original
+ {
+ "%spec_v2int_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %signed_two_vec",
+ "%spec_v2int_isub = OpSpecConstantOp %v2int ISub %signed_one_vec %spec_v2int_iadd",
+ "%spec_v2int_sdiv = OpSpecConstantOp %v2int SDiv %spec_v2int_isub %signed_two_vec",
+ "%spec_v2int_imul = OpSpecConstantOp %v2int IMul %spec_v2int_sdiv %signed_three_vec",
+ "%spec_v2int_iadd_null = OpSpecConstantOp %v2int IAdd %spec_v2int_imul %signed_null_vec",
+
+ "%spec_v2uint_iadd = OpSpecConstantOp %v2uint IAdd %unsigned_three_vec %unsigned_two_vec",
+ "%spec_v2uint_isub = OpSpecConstantOp %v2uint ISub %unsigned_one_vec %spec_v2uint_iadd",
+ "%spec_v2uint_udiv = OpSpecConstantOp %v2uint UDiv %spec_v2uint_isub %unsigned_three_vec",
+ "%spec_v2uint_imul = OpSpecConstantOp %v2uint IMul %spec_v2uint_udiv %unsigned_two_vec",
+ "%spec_v2uint_isub_null = OpSpecConstantOp %v2uint ISub %spec_v2uint_imul %signed_null_vec",
+ },
+ // expected
+ {
+ "%int_5 = OpConstant %int 5",
+ "%int_5_0 = OpConstant %int 5",
+ "%spec_v2int_iadd = OpConstantComposite %v2int %int_5 %int_5",
+ "%int_n4 = OpConstant %int -4",
+ "%int_n4_0 = OpConstant %int -4",
+ "%spec_v2int_isub = OpConstantComposite %v2int %int_n4 %int_n4",
+ "%int_n2 = OpConstant %int -2",
+ "%int_n2_0 = OpConstant %int -2",
+ "%spec_v2int_sdiv = OpConstantComposite %v2int %int_n2 %int_n2",
+ "%int_n6 = OpConstant %int -6",
+ "%int_n6_0 = OpConstant %int -6",
+ "%spec_v2int_imul = OpConstantComposite %v2int %int_n6 %int_n6",
+ "%int_n6_1 = OpConstant %int -6",
+ "%int_n6_2 = OpConstant %int -6",
+ "%spec_v2int_iadd_null = OpConstantComposite %v2int %int_n6 %int_n6",
+
+ "%uint_5 = OpConstant %uint 5",
+ "%uint_5_0 = OpConstant %uint 5",
+ "%spec_v2uint_iadd = OpConstantComposite %v2uint %uint_5 %uint_5",
+ "%uint_4294967292 = OpConstant %uint 4294967292",
+ "%uint_4294967292_0 = OpConstant %uint 4294967292",
+ "%spec_v2uint_isub = OpConstantComposite %v2uint %uint_4294967292 %uint_4294967292",
+ "%uint_1431655764 = OpConstant %uint 1431655764",
+ "%uint_1431655764_0 = OpConstant %uint 1431655764",
+ "%spec_v2uint_udiv = OpConstantComposite %v2uint %uint_1431655764 %uint_1431655764",
+ "%uint_2863311528 = OpConstant %uint 2863311528",
+ "%uint_2863311528_0 = OpConstant %uint 2863311528",
+ "%spec_v2uint_imul = OpConstantComposite %v2uint %uint_2863311528 %uint_2863311528",
+ "%uint_2863311528_1 = OpConstant %uint 2863311528",
+ "%uint_2863311528_2 = OpConstant %uint 2863311528",
+ "%spec_v2uint_isub_null = OpConstantComposite %v2uint %uint_2863311528 %uint_2863311528",
+ },
+ },
+ // vector integer rem, mod
+ {
+ // original
+ {
+ // common constants
+ "%int_7 = OpConstant %int 7",
+ "%v2int_7 = OpConstantComposite %v2int %int_7 %int_7",
+ "%uint_7 = OpConstant %uint 7",
+ "%v2uint_7 = OpConstantComposite %v2uint %uint_7 %uint_7",
+ "%int_minus_7 = OpConstant %int -7",
+ "%v2int_minus_7 = OpConstantComposite %v2int %int_minus_7 %int_minus_7",
+ "%int_minus_3 = OpConstant %int -3",
+ "%v2int_minus_3 = OpConstantComposite %v2int %int_minus_3 %int_minus_3",
+
+ // srem
+ "%7_srem_3 = OpSpecConstantOp %v2int SRem %v2int_7 %signed_three_vec",
+ "%minus_7_srem_3 = OpSpecConstantOp %v2int SRem %v2int_minus_7 %signed_three_vec",
+ "%7_srem_minus_3 = OpSpecConstantOp %v2int SRem %v2int_7 %v2int_minus_3",
+ "%minus_7_srem_minus_3 = OpSpecConstantOp %v2int SRem %v2int_minus_7 %v2int_minus_3",
+ // smod
+ "%7_smod_3 = OpSpecConstantOp %v2int SMod %v2int_7 %signed_three_vec",
+ "%minus_7_smod_3 = OpSpecConstantOp %v2int SMod %v2int_minus_7 %signed_three_vec",
+ "%7_smod_minus_3 = OpSpecConstantOp %v2int SMod %v2int_7 %v2int_minus_3",
+ "%minus_7_smod_minus_3 = OpSpecConstantOp %v2int SMod %v2int_minus_7 %v2int_minus_3",
+ // umod
+ "%7_umod_3 = OpSpecConstantOp %v2uint UMod %v2uint_7 %unsigned_three_vec",
+ },
+ // expected
+ {
+ // common constants
+ "%int_7 = OpConstant %int 7",
+ "%v2int_7 = OpConstantComposite %v2int %int_7 %int_7",
+ "%uint_7 = OpConstant %uint 7",
+ "%v2uint_7 = OpConstantComposite %v2uint %uint_7 %uint_7",
+ "%int_minus_7 = OpConstant %int -7",
+ "%v2int_minus_7 = OpConstantComposite %v2int %int_minus_7 %int_minus_7",
+ "%int_minus_3 = OpConstant %int -3",
+ "%v2int_minus_3 = OpConstantComposite %v2int %int_minus_3 %int_minus_3",
+
+ // srem
+ "%int_1 = OpConstant %int 1",
+ "%int_1_0 = OpConstant %int 1",
+ "%7_srem_3 = OpConstantComposite %v2int %signed_one %signed_one",
+ "%int_n1 = OpConstant %int -1",
+ "%int_n1_0 = OpConstant %int -1",
+ "%minus_7_srem_3 = OpConstantComposite %v2int %int_n1 %int_n1",
+ "%int_1_1 = OpConstant %int 1",
+ "%int_1_2 = OpConstant %int 1",
+ "%7_srem_minus_3 = OpConstantComposite %v2int %signed_one %signed_one",
+ "%int_n1_1 = OpConstant %int -1",
+ "%int_n1_2 = OpConstant %int -1",
+ "%minus_7_srem_minus_3 = OpConstantComposite %v2int %int_n1 %int_n1",
+ // smod
+ "%int_1_3 = OpConstant %int 1",
+ "%int_1_4 = OpConstant %int 1",
+ "%7_smod_3 = OpConstantComposite %v2int %signed_one %signed_one",
+ "%int_2 = OpConstant %int 2",
+ "%int_2_0 = OpConstant %int 2",
+ "%minus_7_smod_3 = OpConstantComposite %v2int %signed_two %signed_two",
+ "%int_n2 = OpConstant %int -2",
+ "%int_n2_0 = OpConstant %int -2",
+ "%7_smod_minus_3 = OpConstantComposite %v2int %int_n2 %int_n2",
+ "%int_n1_3 = OpConstant %int -1",
+ "%int_n1_4 = OpConstant %int -1",
+ "%minus_7_smod_minus_3 = OpConstantComposite %v2int %int_n1 %int_n1",
+ // umod
+ "%uint_1 = OpConstant %uint 1",
+ "%uint_1_0 = OpConstant %uint 1",
+ "%7_umod_3 = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+ },
+ },
+ // vector integer bitwise, shift
+ {
+ // original
+ {
+ "%xor_1_3 = OpSpecConstantOp %v2int BitwiseXor %signed_one_vec %signed_three_vec",
+ "%and_1_2 = OpSpecConstantOp %v2int BitwiseAnd %signed_one_vec %xor_1_3",
+ "%or_1_2 = OpSpecConstantOp %v2int BitwiseOr %signed_one_vec %xor_1_3",
+
+ "%unsigned_31 = OpConstant %uint 31",
+ "%v2unsigned_31 = OpConstantComposite %v2uint %unsigned_31 %unsigned_31",
+ "%unsigned_left_shift_max = OpSpecConstantOp %v2uint ShiftLeftLogical %unsigned_one_vec %v2unsigned_31",
+ "%unsigned_right_shift_logical = OpSpecConstantOp %v2uint ShiftRightLogical %unsigned_left_shift_max %v2unsigned_31",
+ "%signed_right_shift_arithmetic = OpSpecConstantOp %v2int ShiftRightArithmetic %unsigned_left_shift_max %v2unsigned_31",
+ },
+ // expected
+ {
+ "%int_2 = OpConstant %int 2",
+ "%int_2_0 = OpConstant %int 2",
+ "%xor_1_3 = OpConstantComposite %v2int %signed_two %signed_two",
+ "%int_0 = OpConstant %int 0",
+ "%int_0_0 = OpConstant %int 0",
+ "%and_1_2 = OpConstantComposite %v2int %signed_zero %signed_zero",
+ "%int_3 = OpConstant %int 3",
+ "%int_3_0 = OpConstant %int 3",
+ "%or_1_2 = OpConstantComposite %v2int %signed_three %signed_three",
+
+ "%unsigned_31 = OpConstant %uint 31",
+ "%v2unsigned_31 = OpConstantComposite %v2uint %unsigned_31 %unsigned_31",
+ "%uint_2147483648 = OpConstant %uint 2147483648",
+ "%uint_2147483648_0 = OpConstant %uint 2147483648",
+ "%unsigned_left_shift_max = OpConstantComposite %v2uint %uint_2147483648 %uint_2147483648",
+ "%uint_1 = OpConstant %uint 1",
+ "%uint_1_0 = OpConstant %uint 1",
+ "%unsigned_right_shift_logical = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+ "%int_n1 = OpConstant %int -1",
+ "%int_n1_0 = OpConstant %int -1",
+ "%signed_right_shift_arithmetic = OpConstantComposite %v2int %int_n1 %int_n1",
+ },
+ },
+ // Skip folding if any vector operands or components of the operands
+ // have undetermined value.
+ {
+ // original
+ {
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_vec = OpSpecConstantComposite %v2int %signed_zero %spec_int",
+ "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %spec_vec",
+ },
+ // expected
+ {
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_vec = OpSpecConstantComposite %v2int %signed_zero %spec_int",
+ "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %spec_vec",
+ },
+ },
+ // Skip folding if any vector operands are defined by OpUndef
+ {
+ // original
+ {
+ "%undef = OpUndef %int",
+ "%vec = OpConstantComposite %v2int %undef %signed_one",
+ "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %vec",
+ },
+ // expected
+ {
+ "%undef = OpUndef %int",
+ "%vec = OpConstantComposite %v2int %undef %signed_one",
+ "%spec_iadd = OpSpecConstantOp %v2int IAdd %signed_three_vec %vec",
+ },
+ },
+ // clang-format on
+ })));
+
+// Tests for SpecConstantOp CompositeExtract instruction
+INSTANTIATE_TEST_CASE_P(
+ CompositeExtract, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(std::vector<
+ FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // normal vector
+ {
+ // original
+ {
+ "%r = OpSpecConstantOp %int CompositeExtract %signed_three_vec 0",
+ "%x = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 0",
+ "%y = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 1",
+ "%z = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 2",
+ "%w = OpSpecConstantOp %int CompositeExtract %v4int_0_1_2_3 3",
+ },
+ // expected
+ {
+ "%r = OpConstant %int 3",
+ "%x = OpConstant %int 0",
+ "%y = OpConstant %int 1",
+ "%z = OpConstant %int 2",
+ "%w = OpConstant %int 3",
+ },
+ },
+ // null vector
+ {
+ // original
+ {
+ "%x = OpSpecConstantOp %int CompositeExtract %signed_null_vec 0",
+ "%y = OpSpecConstantOp %int CompositeExtract %signed_null_vec 1",
+ "%null_v4int = OpConstantNull %v4int",
+ "%z = OpSpecConstantOp %int CompositeExtract %signed_null_vec 2",
+ },
+ // expected
+ {
+ "%x = OpConstantNull %int",
+ "%y = OpConstantNull %int",
+ "%null_v4int = OpConstantNull %v4int",
+ "%z = OpConstantNull %int",
+ }
+ },
+ // normal flat struct
+ {
+ // original
+ {
+ "%float_1 = OpConstant %float 1",
+ "%flat_1 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1",
+ "%extract_bool = OpSpecConstantOp %bool CompositeExtract %flat_1 0",
+ "%extract_int = OpSpecConstantOp %int CompositeExtract %flat_1 1",
+ "%extract_float_1 = OpSpecConstantOp %float CompositeExtract %flat_1 2",
+ // foldable composite constants built with OpSpecConstantComposite
+ // should also be processed.
+ "%flat_2 = OpSpecConstantComposite %flat_struct %bool_true %signed_null %float_1",
+ "%extract_float_2 = OpSpecConstantOp %float CompositeExtract %flat_2 2",
+ },
+ // expected
+ {
+ "%float_1 = OpConstant %float 1",
+ "%flat_1 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1",
+ "%extract_bool = OpConstantTrue %bool",
+ "%extract_int = OpConstantNull %int",
+ "%extract_float_1 = OpConstant %float 1",
+ "%flat_2 = OpConstantComposite %flat_struct %bool_true %signed_null %float_1",
+ "%extract_float_2 = OpConstant %float 1",
+ },
+ },
+ // null flat struct
+ {
+ // original
+ {
+ "%flat = OpConstantNull %flat_struct",
+ "%extract_bool = OpSpecConstantOp %bool CompositeExtract %flat 0",
+ "%extract_int = OpSpecConstantOp %int CompositeExtract %flat 1",
+ "%extract_float = OpSpecConstantOp %float CompositeExtract %flat 2",
+ },
+ // expected
+ {
+ "%flat = OpConstantNull %flat_struct",
+ "%extract_bool = OpConstantNull %bool",
+ "%extract_int = OpConstantNull %int",
+ "%extract_float = OpConstantNull %float",
+ },
+ },
+ // normal nested struct
+ {
+ // original
+ {
+ "%float_1 = OpConstant %float 1",
+ "%inner = OpConstantComposite %inner_struct %bool_true %signed_null %float_1",
+ "%outer = OpConstantComposite %outer_struct %inner %signed_one",
+ "%extract_inner = OpSpecConstantOp %inner_struct CompositeExtract %outer 0",
+ "%extract_int = OpSpecConstantOp %int CompositeExtract %outer 1",
+ "%extract_inner_float = OpSpecConstantOp %int CompositeExtract %outer 0 2",
+ },
+ // expected
+ {
+ "%float_1 = OpConstant %float 1",
+ "%inner = OpConstantComposite %inner_struct %bool_true %signed_null %float_1",
+ "%outer = OpConstantComposite %outer_struct %inner %signed_one",
+ "%extract_inner = OpConstantComposite %flat_struct %bool_true %signed_null %float_1",
+ "%extract_int = OpConstant %int 1",
+ "%extract_inner_float = OpConstant %float 1",
+ },
+ },
+ // null nested struct
+ {
+ // original
+ {
+ "%outer = OpConstantNull %outer_struct",
+ "%extract_inner = OpSpecConstantOp %inner_struct CompositeExtract %outer 0",
+ "%extract_int = OpSpecConstantOp %int CompositeExtract %outer 1",
+ "%extract_inner_float = OpSpecConstantOp %float CompositeExtract %outer 0 2",
+ },
+ // expected
+ {
+ "%outer = OpConstantNull %outer_struct",
+ "%extract_inner = OpConstantNull %inner_struct",
+ "%extract_int = OpConstantNull %int",
+ "%extract_inner_float = OpConstantNull %float",
+ },
+ },
+ // skip folding if the any composite constant's value are not fully
+ // determined, even though the extracting target might have
+ // determined value.
+ {
+ // original
+ {
+ "%float_1 = OpConstant %float 1",
+ "%spec_float = OpSpecConstant %float 1",
+ "%spec_inner = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float",
+ "%spec_outer = OpSpecConstantComposite %outer_struct %spec_inner %signed_one",
+ "%spec_vec = OpSpecConstantComposite %v2float %spec_float %float_1",
+ "%extract_inner = OpSpecConstantOp %int CompositeExtract %spec_inner 1",
+ "%extract_outer = OpSpecConstantOp %int CompositeExtract %spec_outer 1",
+ "%extract_vec = OpSpecConstantOp %float CompositeExtract %spec_vec 1",
+ },
+ // expected
+ {
+ "%float_1 = OpConstant %float 1",
+ "%spec_float = OpSpecConstant %float 1",
+ "%spec_inner = OpSpecConstantComposite %inner_struct %bool_true %signed_null %spec_float",
+ "%spec_outer = OpSpecConstantComposite %outer_struct %spec_inner %signed_one",
+ "%spec_vec = OpSpecConstantComposite %v2float %spec_float %float_1",
+ "%extract_inner = OpSpecConstantOp %int CompositeExtract %spec_inner 1",
+ "%extract_outer = OpSpecConstantOp %int CompositeExtract %spec_outer 1",
+ "%extract_vec = OpSpecConstantOp %float CompositeExtract %spec_vec 1",
+ },
+ },
+ // skip if the composite constant depends on the result of OpUndef,
+ // even though the composite extract target element does not depends
+ // on the OpUndef.
+ {
+ // original
+ {
+ "%undef = OpUndef %float",
+ "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef",
+ "%outer = OpConstantComposite %outer_struct %inner %signed_one",
+ "%extract_inner = OpSpecConstantOp %int CompositeExtract %inner 1",
+ "%extract_outer = OpSpecConstantOp %int CompositeExtract %outer 1",
+ },
+ // expected
+ {
+ "%undef = OpUndef %float",
+ "%inner = OpConstantComposite %inner_struct %bool_true %signed_one %undef",
+ "%outer = OpConstantComposite %outer_struct %inner %signed_one",
+ "%extract_inner = OpSpecConstantOp %int CompositeExtract %inner 1",
+ "%extract_outer = OpSpecConstantOp %int CompositeExtract %outer 1",
+ },
+ },
+ // TODO(qining): Add tests for Array and other composite type constants.
+ // clang-format on
+ })));
+
+// Tests the swizzle operations for spec const vectors.
+INSTANTIATE_TEST_CASE_P(
+ VectorShuffle, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(std::vector<
+ FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // normal vector
+ {
+ // original
+ {
+ "%xy = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 0 1",
+ "%yz = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 1 2",
+ "%zw = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 2 3",
+ "%wx = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 3 0",
+ "%xx = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 0 0",
+ "%yyy = OpSpecConstantOp %v3int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 1 1 1",
+ "%wwww = OpSpecConstantOp %v4int VectorShuffle %v4int_0_1_2_3 %v4int_0_1_2_3 2 2 2 2",
+ },
+ // expected
+ {
+ "%xy = OpConstantComposite %v2int %signed_zero %signed_one",
+ "%yz = OpConstantComposite %v2int %signed_one %signed_two",
+ "%zw = OpConstantComposite %v2int %signed_two %signed_three",
+ "%wx = OpConstantComposite %v2int %signed_three %signed_zero",
+ "%xx = OpConstantComposite %v2int %signed_zero %signed_zero",
+ "%yyy = OpConstantComposite %v3int %signed_one %signed_one %signed_one",
+ "%wwww = OpConstantComposite %v4int %signed_two %signed_two %signed_two %signed_two",
+ },
+ },
+ // null vector
+ {
+ // original
+ {
+ "%a = OpSpecConstantOp %v2int VectorShuffle %signed_null_vec %v4int_0_1_2_3 0 1",
+ "%b = OpSpecConstantOp %v2int VectorShuffle %signed_null_vec %v4int_0_1_2_3 2 3",
+ "%c = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %signed_null_vec 3 4",
+ "%d = OpSpecConstantOp %v2int VectorShuffle %signed_null_vec %signed_null_vec 1 2",
+ },
+ // expected
+ {
+ "%60 = OpConstantNull %int",
+ "%a = OpConstantComposite %v2int %signed_null %signed_null",
+ "%62 = OpConstantNull %int",
+ "%b = OpConstantComposite %v2int %signed_zero %signed_one",
+ "%64 = OpConstantNull %int",
+ "%c = OpConstantComposite %v2int %signed_three %signed_null",
+ "%66 = OpConstantNull %int",
+ "%d = OpConstantComposite %v2int %signed_null %signed_null",
+ }
+ },
+ // skip if any of the components of the vector operands do not have
+ // determined value, even though the result vector might not be
+ // built with those undermined values.
+ {
+ // original
+ {
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_ivec = OpSpecConstantComposite %v2int %signed_null %spec_int",
+ "%a = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 0 1",
+ "%b = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 3 4",
+ },
+ // expected
+ {
+ "%spec_int = OpSpecConstant %int 1",
+ "%spec_ivec = OpSpecConstantComposite %v2int %signed_null %spec_int",
+ "%a = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 0 1",
+ "%b = OpSpecConstantOp %v2int VectorShuffle %v4int_0_1_2_3 %spec_ivec 3 4",
+ },
+ },
+ // Skip if any components of the two vector operands depend on
+ // the result of OpUndef. Even though the selected components do
+ // not depend on the OpUndef result.
+ {
+ // original
+ {
+ "%undef = OpUndef %int",
+ "%vec_1 = OpConstantComposite %v2int %undef %signed_one",
+ "%dep = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 0 3",
+ "%not_dep_element = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 1 3",
+ "%no_dep_vector = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 2 3",
+ },
+ // expected
+ {
+ "%undef = OpUndef %int",
+ "%vec_1 = OpConstantComposite %v2int %undef %signed_one",
+ "%dep = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 0 3",
+ "%not_dep_element = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 1 3",
+ "%no_dep_vector = OpSpecConstantOp %v2int VectorShuffle %vec_1 %signed_three_vec 2 3",
+ },
+ },
+ // clang-format on
+ })));
+
+// Test with long use-def chain.
+INSTANTIATE_TEST_CASE_P(
+ LongDefUseChain, FoldSpecConstantOpAndCompositePassTest,
+ ::testing::ValuesIn(std::vector<
+ FoldSpecConstantOpAndCompositePassTestCase>({
+ // clang-format off
+ // Long Def-Use chain with binary operations.
+ {
+ // original
+ {
+ "%array_size = OpConstant %int 4",
+ "%type_arr_int_4 = OpTypeArray %int %array_size",
+ "%spec_int_0 = OpConstant %int 100",
+ "%spec_int_1 = OpConstant %int 1",
+ "%spec_int_2 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_1",
+ "%spec_int_3 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_2",
+ "%spec_int_4 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_3",
+ "%spec_int_5 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_4",
+ "%spec_int_6 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_5",
+ "%spec_int_7 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_6",
+ "%spec_int_8 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_7",
+ "%spec_int_9 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_8",
+ "%spec_int_10 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_9",
+ "%spec_int_11 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_10",
+ "%spec_int_12 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_11",
+ "%spec_int_13 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_12",
+ "%spec_int_14 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_13",
+ "%spec_int_15 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_14",
+ "%spec_int_16 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_15",
+ "%spec_int_17 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_16",
+ "%spec_int_18 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_17",
+ "%spec_int_19 = OpSpecConstantOp %int IAdd %spec_int_0 %spec_int_18",
+ "%spec_int_20 = OpSpecConstantOp %int ISub %spec_int_0 %spec_int_19",
+ "%used_vec_a = OpSpecConstantComposite %v2int %spec_int_18 %spec_int_19",
+ "%used_vec_b = OpSpecConstantOp %v2int IMul %used_vec_a %used_vec_a",
+ "%spec_int_21 = OpSpecConstantOp %int CompositeExtract %used_vec_b 0",
+ "%array = OpConstantComposite %type_arr_int_4 %spec_int_20 %spec_int_20 %spec_int_21 %spec_int_21",
+ // Spec constants whose values can not be fully resolved should
+ // not be processed.
+ "%spec_int_22 = OpSpecConstant %int 123",
+ "%spec_int_23 = OpSpecConstantOp %int IAdd %spec_int_22 %signed_one",
+ },
+ // expected
+ {
+ "%array_size = OpConstant %int 4",
+ "%type_arr_int_4 = OpTypeArray %int %array_size",
+ "%spec_int_0 = OpConstant %int 100",
+ "%spec_int_1 = OpConstant %int 1",
+ "%spec_int_2 = OpConstant %int 101",
+ "%spec_int_3 = OpConstant %int -1",
+ "%spec_int_4 = OpConstant %int 99",
+ "%spec_int_5 = OpConstant %int 1",
+ "%spec_int_6 = OpConstant %int 101",
+ "%spec_int_7 = OpConstant %int -1",
+ "%spec_int_8 = OpConstant %int 99",
+ "%spec_int_9 = OpConstant %int 1",
+ "%spec_int_10 = OpConstant %int 101",
+ "%spec_int_11 = OpConstant %int -1",
+ "%spec_int_12 = OpConstant %int 99",
+ "%spec_int_13 = OpConstant %int 1",
+ "%spec_int_14 = OpConstant %int 101",
+ "%spec_int_15 = OpConstant %int -1",
+ "%spec_int_16 = OpConstant %int 101",
+ "%spec_int_17 = OpConstant %int 201",
+ "%spec_int_18 = OpConstant %int -101",
+ "%spec_int_19 = OpConstant %int -1",
+ "%spec_int_20 = OpConstant %int 101",
+ "%used_vec_a = OpConstantComposite %v2int %spec_int_18 %spec_int_19",
+ "%int_10201 = OpConstant %int 10201",
+ "%int_1 = OpConstant %int 1",
+ "%used_vec_b = OpConstantComposite %v2int %int_10201 %signed_one",
+ "%spec_int_21 = OpConstant %int 10201",
+ "%array = OpConstantComposite %type_arr_int_4 %spec_int_20 %spec_int_20 %spec_int_21 %spec_int_21",
+ "%spec_int_22 = OpSpecConstant %int 123",
+ "%spec_int_23 = OpSpecConstantOp %int IAdd %spec_int_22 %signed_one",
+ },
+ },
+ // Long Def-Use chain with swizzle
+ })));
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/fold_test.cpp b/test/opt/fold_test.cpp
new file mode 100644
index 0000000..487c18a
--- /dev/null
+++ b/test/opt/fold_test.cpp
@@ -0,0 +1,6198 @@
+// Copyright (c) 2016 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 <limits>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/def_use_manager.h"
+#include "source/opt/fold.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/module.h"
+#include "spirv-tools/libspirv.hpp"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::Contains;
+
+std::string Disassemble(const std::string& original, IRContext* context,
+ uint32_t disassemble_options = 0) {
+ std::vector<uint32_t> optimized_bin;
+ context->module()->ToBinary(&optimized_bin, true);
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+ SpirvTools tools(target_env);
+ std::string optimized_asm;
+ EXPECT_TRUE(
+ tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options))
+ << "Disassembling failed for shader:\n"
+ << original << std::endl;
+ return optimized_asm;
+}
+
+void Match(const std::string& original, IRContext* context,
+ uint32_t disassemble_options = 0) {
+ std::string disassembly = Disassemble(original, context, disassemble_options);
+ auto match_result = effcee::Match(disassembly, original);
+ EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+ << match_result.message() << "\nChecking result:\n"
+ << disassembly;
+}
+
+template <class ResultType>
+struct InstructionFoldingCase {
+ InstructionFoldingCase(const std::string& tb, uint32_t id, ResultType result)
+ : test_body(tb), id_to_fold(id), expected_result(result) {}
+
+ std::string test_body;
+ uint32_t id_to_fold;
+ ResultType expected_result;
+};
+
+using IntegerInstructionFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<uint32_t>>;
+
+TEST_P(IntegerInstructionFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_TRUE(succeeded);
+ if (inst != nullptr) {
+ EXPECT_EQ(inst->opcode(), SpvOpCopyObject);
+ inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
+ EXPECT_EQ(inst->opcode(), SpvOpConstant);
+ analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+ const analysis::IntConstant* result =
+ const_mrg->GetConstantFromInst(inst)->AsIntConstant();
+ EXPECT_NE(result, nullptr);
+ if (result != nullptr) {
+ EXPECT_EQ(result->GetU32BitValue(), tc.expected_result);
+ }
+ }
+}
+
+// Returns a common SPIR-V header for all of the test that follow.
+#define INT_0_ID 100
+#define TRUE_ID 101
+#define VEC2_0_ID 102
+#define INT_7_ID 103
+#define FLOAT_0_ID 104
+#define DOUBLE_0_ID 105
+#define VEC4_0_ID 106
+#define DVEC4_0_ID 106
+#define HALF_0_ID 108
+const std::string& Header() {
+ static const std::string header = R"(OpCapability Shader
+OpCapability Float16
+OpCapability Float64
+OpCapability Int16
+OpCapability Int64
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+%void = OpTypeVoid
+%void_func = OpTypeFunction %void
+%bool = OpTypeBool
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%half = OpTypeFloat 16
+%101 = OpConstantTrue %bool ; Need a def with an numerical id to define id maps.
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%bool_null = OpConstantNull %bool
+%short = OpTypeInt 16 1
+%int = OpTypeInt 32 1
+%long = OpTypeInt 64 1
+%uint = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%v4int = OpTypeVector %int 4
+%v4float = OpTypeVector %float 4
+%v4double = OpTypeVector %double 4
+%v2float = OpTypeVector %float 2
+%v2double = OpTypeVector %double 2
+%v2bool = OpTypeVector %bool 2
+%struct_v2int_int_int = OpTypeStruct %v2int %int %int
+%_ptr_int = OpTypePointer Function %int
+%_ptr_uint = OpTypePointer Function %uint
+%_ptr_bool = OpTypePointer Function %bool
+%_ptr_float = OpTypePointer Function %float
+%_ptr_double = OpTypePointer Function %double
+%_ptr_half = OpTypePointer Function %half
+%_ptr_long = OpTypePointer Function %long
+%_ptr_v2int = OpTypePointer Function %v2int
+%_ptr_v4int = OpTypePointer Function %v4int
+%_ptr_v4float = OpTypePointer Function %v4float
+%_ptr_v4double = OpTypePointer Function %v4double
+%_ptr_struct_v2int_int_int = OpTypePointer Function %struct_v2int_int_int
+%_ptr_v2float = OpTypePointer Function %v2float
+%_ptr_v2double = OpTypePointer Function %v2double
+%short_0 = OpConstant %short 0
+%short_2 = OpConstant %short 2
+%short_3 = OpConstant %short 3
+%100 = OpConstant %int 0 ; Need a def with an numerical id to define id maps.
+%103 = OpConstant %int 7 ; Need a def with an numerical id to define id maps.
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%int_n24 = OpConstant %int -24
+%int_min = OpConstant %int -2147483648
+%int_max = OpConstant %int 2147483647
+%long_0 = OpConstant %long 0
+%long_2 = OpConstant %long 2
+%long_3 = OpConstant %long 3
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%uint_4 = OpConstant %uint 4
+%uint_32 = OpConstant %uint 32
+%uint_42 = OpConstant %uint 42
+%uint_max = OpConstant %uint 4294967295
+%v2int_undef = OpUndef %v2int
+%v2int_0_0 = OpConstantComposite %v2int %int_0 %int_0
+%v2int_1_0 = OpConstantComposite %v2int %int_1 %int_0
+%v2int_2_2 = OpConstantComposite %v2int %int_2 %int_2
+%v2int_2_3 = OpConstantComposite %v2int %int_2 %int_3
+%v2int_3_2 = OpConstantComposite %v2int %int_3 %int_2
+%v2int_4_4 = OpConstantComposite %v2int %int_4 %int_4
+%v2bool_null = OpConstantNull %v2bool
+%v2bool_true_false = OpConstantComposite %v2bool %true %false
+%v2bool_false_true = OpConstantComposite %v2bool %false %true
+%struct_v2int_int_int_null = OpConstantNull %struct_v2int_int_int
+%v2int_null = OpConstantNull %v2int
+%102 = OpConstantComposite %v2int %103 %103
+%v4int_0_0_0_0 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0
+%struct_undef_0_0 = OpConstantComposite %struct_v2int_int_int %v2int_undef %int_0 %int_0
+%float_n1 = OpConstant %float -1
+%104 = OpConstant %float 0 ; Need a def with an numerical id to define id maps.
+%float_null = OpConstantNull %float
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%float_3 = OpConstant %float 3
+%float_4 = OpConstant %float 4
+%float_0p5 = OpConstant %float 0.5
+%v2float_0_0 = OpConstantComposite %v2float %float_0 %float_0
+%v2float_2_2 = OpConstantComposite %v2float %float_2 %float_2
+%v2float_2_3 = OpConstantComposite %v2float %float_2 %float_3
+%v2float_3_2 = OpConstantComposite %v2float %float_3 %float_2
+%v2float_4_4 = OpConstantComposite %v2float %float_4 %float_4
+%v2float_2_0p5 = OpConstantComposite %v2float %float_2 %float_0p5
+%v2float_null = OpConstantNull %v2float
+%double_n1 = OpConstant %double -1
+%105 = OpConstant %double 0 ; Need a def with an numerical id to define id maps.
+%double_null = OpConstantNull %double
+%double_0 = OpConstant %double 0
+%double_1 = OpConstant %double 1
+%double_2 = OpConstant %double 2
+%double_3 = OpConstant %double 3
+%double_4 = OpConstant %double 4
+%double_0p5 = OpConstant %double 0.5
+%v2double_0_0 = OpConstantComposite %v2double %double_0 %double_0
+%v2double_2_2 = OpConstantComposite %v2double %double_2 %double_2
+%v2double_2_3 = OpConstantComposite %v2double %double_2 %double_3
+%v2double_3_2 = OpConstantComposite %v2double %double_3 %double_2
+%v2double_4_4 = OpConstantComposite %v2double %double_4 %double_4
+%v2double_2_0p5 = OpConstantComposite %v2double %double_2 %double_0p5
+%v2double_null = OpConstantNull %v2double
+%108 = OpConstant %half 0
+%half_1 = OpConstant %half 1
+%106 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%v4float_0_0_0_0 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%v4float_0_0_0_1 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
+%v4float_0_1_0_0 = OpConstantComposite %v4float %float_0 %float_1 %float_null %float_0
+%v4float_1_1_1_1 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+%107 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0
+%v4double_0_0_0_0 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_0
+%v4double_0_0_0_1 = OpConstantComposite %v4double %double_0 %double_0 %double_0 %double_1
+%v4double_0_1_0_0 = OpConstantComposite %v4double %double_0 %double_1 %double_null %double_0
+%v4double_1_1_1_1 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_1
+%v4double_1_1_1_0p5 = OpConstantComposite %v4double %double_1 %double_1 %double_1 %double_0p5
+%v4double_null = OpConstantNull %v4double
+%v4float_n1_2_1_3 = OpConstantComposite %v4float %float_n1 %float_2 %float_1 %float_3
+)";
+
+ return header;
+}
+
+// Returns the header with definitions of float NaN and double NaN. Since FC
+// "; CHECK: [[double_n0:%\\w+]] = OpConstant [[double]] -0\n" finds
+// %double_nan = OpConstant %double -0x1.8p+1024 instead of
+// %double_n0 = OpConstant %double -0,
+// we separates those definitions from Header().
+const std::string& HeaderWithNaN() {
+ static const std::string headerWithNaN =
+ Header() +
+ R"(%float_nan = OpConstant %float -0x1.8p+128
+%double_nan = OpConstant %double -0x1.8p+1024
+)";
+
+ return headerWithNaN;
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold 0*n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpIMul %int %int_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: fold n*0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpIMul %int %load %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 2: fold 0/n (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSDiv %int %int_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: fold n/0 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSDiv %int %load %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 4: fold 0/n (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpUDiv %uint %uint_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 5: fold n/0 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSDiv %int %load %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 6: fold 0 remainder n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSRem %int %int_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 7: fold n remainder 0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSRem %int %load %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 8: fold 0%n (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSMod %int %int_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 9: fold n%0 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSMod %int %load %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 10: fold 0%n (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpUMod %uint %uint_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 11: fold n%0 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpUMod %uint %load %uint_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 12: fold n << 32
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpShiftLeftLogical %uint %load %uint_32\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 13: fold n >> 32
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpShiftRightLogical %uint %load %uint_32\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 14: fold n | 0xFFFFFFFF
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpBitwiseOr %uint %load %uint_max\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0xFFFFFFFF),
+ // Test case 15: fold 0xFFFFFFFF | n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpBitwiseOr %uint %uint_max %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0xFFFFFFFF),
+ // Test case 16: fold n & 0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpBitwiseAnd %uint %load %uint_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 17: fold 1/0 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpSDiv %int %int_1 %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 18: fold 1/0 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpUDiv %uint %uint_1 %uint_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 19: fold OpSRem 1 0 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpSRem %int %int_1 %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 20: fold 1%0 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpSMod %int %int_1 %int_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 21: fold 1%0 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpUMod %uint %uint_1 %uint_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 22: fold unsigned n >> 42 (undefined, so set to zero).
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpShiftRightLogical %uint %load %uint_42\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 23: fold signed n >> 42 (undefined, so set to zero).
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpShiftRightLogical %int %load %uint_42\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 24: fold n << 42 (undefined, so set to zero).
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpShiftLeftLogical %int %load %uint_42\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 25: fold -24 >> 32 (defined as -1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpShiftRightArithmetic %int %int_n24 %uint_32\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, -1),
+ // Test case 26: fold 2 >> 32 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpShiftRightArithmetic %int %int_2 %uint_32\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 27: fold 2 >> 32 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpShiftRightLogical %int %int_2 %uint_32\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 28: fold 2 << 32
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpShiftLeftLogical %int %int_2 %uint_32\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 29: fold -INT_MIN
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpSNegate %int %int_min\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, std::numeric_limits<int32_t>::min())
+));
+// clang-format on
+
+using IntVectorInstructionFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<std::vector<uint32_t>>>;
+
+TEST_P(IntVectorInstructionFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ SpvOp original_opcode = inst->opcode();
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_EQ(succeeded, inst == nullptr || inst->opcode() != original_opcode);
+ if (succeeded && inst != nullptr) {
+ EXPECT_EQ(inst->opcode(), SpvOpCopyObject);
+ inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
+ std::vector<SpvOp> opcodes = {SpvOpConstantComposite};
+ EXPECT_THAT(opcodes, Contains(inst->opcode()));
+ analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+ const analysis::Constant* result = const_mrg->GetConstantFromInst(inst);
+ EXPECT_NE(result, nullptr);
+ if (result != nullptr) {
+ const std::vector<const analysis::Constant*>& componenets =
+ result->AsVectorConstant()->GetComponents();
+ EXPECT_EQ(componenets.size(), tc.expected_result.size());
+ for (size_t i = 0; i < componenets.size(); i++) {
+ EXPECT_EQ(tc.expected_result[i], componenets[i]->GetU32());
+ }
+ }
+ }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, IntVectorInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: fold 0*n
+ InstructionFoldingCase<std::vector<uint32_t>>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpVectorShuffle %v2int %v2int_2_2 %v2int_2_3 0 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, {2,3}),
+ InstructionFoldingCase<std::vector<uint32_t>>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpVectorShuffle %v2int %v2int_null %v2int_2_3 0 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, {0,3}),
+ InstructionFoldingCase<std::vector<uint32_t>>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpVectorShuffle %v2int %v2int_null %v2int_2_3 4294967295 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, {0,0}),
+ InstructionFoldingCase<std::vector<uint32_t>>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpVectorShuffle %v2int %v2int_null %v2int_2_3 0 4294967295 \n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, {0,0})
+));
+// clang-format on
+
+using BooleanInstructionFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(BooleanInstructionFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_TRUE(succeeded);
+ if (inst != nullptr) {
+ EXPECT_EQ(inst->opcode(), SpvOpCopyObject);
+ inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
+ std::vector<SpvOp> bool_opcodes = {SpvOpConstantTrue, SpvOpConstantFalse};
+ EXPECT_THAT(bool_opcodes, Contains(inst->opcode()));
+ analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+ const analysis::BoolConstant* result =
+ const_mrg->GetConstantFromInst(inst)->AsBoolConstant();
+ EXPECT_NE(result, nullptr);
+ if (result != nullptr) {
+ EXPECT_EQ(result->value(), tc.expected_result);
+ }
+ }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold true || n
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%2 = OpLogicalOr %bool %true %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 1: fold n || true
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%2 = OpLogicalOr %bool %load %true\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold false && n
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%2 = OpLogicalAnd %bool %false %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 3: fold n && false
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%2 = OpLogicalAnd %bool %load %false\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 4: fold n < 0 (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpULessThan %bool %load %uint_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 5: fold UINT_MAX < n (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpULessThan %bool %uint_max %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 6: fold INT_MAX < n (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSLessThan %bool %int_max %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 7: fold n < INT_MIN (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSLessThan %bool %load %int_min\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 8: fold 0 > n (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpUGreaterThan %bool %uint_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 9: fold n > UINT_MAX (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpUGreaterThan %bool %load %uint_max\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 10: fold n > INT_MAX (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSGreaterThan %bool %load %int_max\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 11: fold INT_MIN > n (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpSGreaterThan %bool %int_min %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 12: fold 0 <= n (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpULessThanEqual %bool %uint_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 13: fold n <= UINT_MAX (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpULessThanEqual %bool %load %uint_max\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 14: fold INT_MIN <= n (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSLessThanEqual %bool %int_min %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 15: fold n <= INT_MAX (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSLessThanEqual %bool %load %int_max\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 16: fold n >= 0 (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpUGreaterThanEqual %bool %load %uint_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 17: fold UINT_MAX >= n (unsigned)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load = OpLoad %uint %n\n" +
+ "%2 = OpUGreaterThanEqual %bool %uint_max %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 18: fold n >= INT_MIN (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSGreaterThanEqual %bool %load %int_min\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 19: fold INT_MAX >= n (signed)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSGreaterThanEqual %bool %int_max %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(FClampAndCmpLHS, BooleanInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: fold 0.0 > clamp(n, 0.0, 1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFOrdGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold 0.0 > clamp(n, -1.0, -1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_n1\n" +
+ "%2 = OpFOrdGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold 0.0 >= clamp(n, 1, 2)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 3: fold 0.0 >= clamp(n, -1.0, 0.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 4: fold 0.0 <= clamp(n, 0.0, 1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFOrdLessThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 5: fold 0.0 <= clamp(n, -1.0, -1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_n1\n" +
+ "%2 = OpFOrdLessThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 6: fold 0.0 < clamp(n, 1, 2)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFOrdLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 7: fold 0.0 < clamp(n, -1.0, 0.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFOrdLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 8: fold 0.0 > clamp(n, 0.0, 1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFUnordGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 9: fold 0.0 > clamp(n, -1.0, -1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_n1\n" +
+ "%2 = OpFUnordGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 10: fold 0.0 >= clamp(n, 1, 2)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 11: fold 0.0 >= clamp(n, -1.0, 0.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 12: fold 0.0 <= clamp(n, 0.0, 1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFUnordLessThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 13: fold 0.0 <= clamp(n, -1.0, -1.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_n1\n" +
+ "%2 = OpFUnordLessThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 14: fold 0.0 < clamp(n, 1, 2)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFUnordLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 15: fold 0.0 < clamp(n, -1.0, 0.0)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFUnordLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false)
+));
+
+INSTANTIATE_TEST_CASE_P(FClampAndCmpRHS, BooleanInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: fold clamp(n, 0.0, 1.0) > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFOrdGreaterThan %bool %clamp %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold clamp(n, 1.0, 1.0) > 0.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_1\n" +
+ "%2 = OpFOrdGreaterThan %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold clamp(n, 1, 2) >= 0.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 3: fold clamp(n, 1.0, 2.0) >= 3.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %clamp %float_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 4: fold clamp(n, 0.0, 1.0) <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFOrdLessThanEqual %bool %clamp %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 5: fold clamp(n, 1.0, 2.0) <= 0.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFOrdLessThanEqual %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 6: fold clamp(n, 1, 2) < 3
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFOrdLessThan %bool %clamp %float_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 7: fold clamp(n, -1.0, 0.0) < -1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFOrdLessThan %bool %clamp %float_n1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 8: fold clamp(n, 0.0, 1.0) > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFUnordGreaterThan %bool %clamp %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 9: fold clamp(n, 1.0, 2.0) > 0.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFUnordGreaterThan %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 10: fold clamp(n, 1, 2) >= 3.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %clamp %float_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 11: fold clamp(n, -1.0, 0.0) >= -1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %clamp %float_n1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 12: fold clamp(n, 0.0, 1.0) <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFUnordLessThanEqual %bool %clamp %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 13: fold clamp(n, 1.0, 1.0) <= 0.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_1\n" +
+ "%2 = OpFUnordLessThanEqual %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 14: fold clamp(n, 1, 2) < 3
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_1 %float_2\n" +
+ "%2 = OpFUnordLessThan %bool %clamp %float_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 15: fold clamp(n, -1.0, 0.0) < -1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFUnordLessThan %bool %clamp %float_n1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 16: fold clamp(n, -1.0, 0.0) < -1.0 (one test for double)
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%ld = OpLoad %double %n\n" +
+ "%clamp = OpExtInst %double %1 FClamp %ld %double_n1 %double_0\n" +
+ "%2 = OpFUnordLessThan %bool %clamp %double_n1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false)
+));
+// clang-format on
+
+using FloatInstructionFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<float>>;
+
+TEST_P(FloatInstructionFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_TRUE(succeeded);
+ if (inst != nullptr) {
+ EXPECT_EQ(inst->opcode(), SpvOpCopyObject);
+ inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
+ EXPECT_EQ(inst->opcode(), SpvOpConstant);
+ analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+ const analysis::FloatConstant* result =
+ const_mrg->GetConstantFromInst(inst)->AsFloatConstant();
+ EXPECT_NE(result, nullptr);
+ if (result != nullptr) {
+ EXPECT_EQ(result->GetFloatValue(), tc.expected_result);
+ }
+ }
+}
+
+// Not testing NaNs because there are no expectations concerning NaNs according
+// to the "Precision and Operation of SPIR-V Instructions" section of the Vulkan
+// specification.
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(FloatConstantFoldingTest, FloatInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Fold 2.0 - 1.0
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFSub %float %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 1.0),
+ // Test case 1: Fold 2.0 + 1.0
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFAdd %float %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3.0),
+ // Test case 2: Fold 3.0 * 2.0
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFMul %float %float_3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 6.0),
+ // Test case 3: Fold 1.0 / 2.0
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFDiv %float %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0.5),
+ // Test case 4: Fold 1.0 / 0.0
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFDiv %float %float_1 %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, std::numeric_limits<float>::infinity()),
+ // Test case 5: Fold -1.0 / 0.0
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFDiv %float %float_n1 %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, -std::numeric_limits<float>::infinity()),
+ // Test case 6: Fold (2.0, 3.0) dot (2.0, 0.5)
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpDot %float %v2float_2_3 %v2float_2_0p5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 5.5f),
+ // Test case 7: Fold (0.0, 0.0) dot v
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %v\n" +
+ "%3 = OpDot %float %v2float_0_0 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 8: Fold v dot (0.0, 0.0)
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %v\n" +
+ "%3 = OpDot %float %2 %v2float_0_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 9: Fold Null dot v
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %v\n" +
+ "%3 = OpDot %float %v2float_null %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 10: Fold v dot Null
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %v\n" +
+ "%3 = OpDot %float %2 %v2float_null\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 11: Fold -2.0
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFNegate %float %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, -2)
+));
+// clang-format on
+
+using DoubleInstructionFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<double>>;
+
+TEST_P(DoubleInstructionFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_TRUE(succeeded);
+ if (inst != nullptr) {
+ EXPECT_EQ(inst->opcode(), SpvOpCopyObject);
+ inst = def_use_mgr->GetDef(inst->GetSingleWordInOperand(0));
+ EXPECT_EQ(inst->opcode(), SpvOpConstant);
+ analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+ const analysis::FloatConstant* result =
+ const_mrg->GetConstantFromInst(inst)->AsFloatConstant();
+ EXPECT_NE(result, nullptr);
+ if (result != nullptr) {
+ EXPECT_EQ(result->GetDoubleValue(), tc.expected_result);
+ }
+ }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(DoubleConstantFoldingTest, DoubleInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Fold 2.0 - 1.0
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFSub %double %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 1.0),
+ // Test case 1: Fold 2.0 + 1.0
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFAdd %double %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3.0),
+ // Test case 2: Fold 3.0 * 2.0
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFMul %double %double_3 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 6.0),
+ // Test case 3: Fold 1.0 / 2.0
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFDiv %double %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0.5),
+ // Test case 4: Fold 1.0 / 0.0
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFDiv %double %double_1 %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, std::numeric_limits<double>::infinity()),
+ // Test case 5: Fold -1.0 / 0.0
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFDiv %double %double_n1 %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, -std::numeric_limits<double>::infinity()),
+ // Test case 6: Fold (2.0, 3.0) dot (2.0, 0.5)
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpDot %double %v2double_2_3 %v2double_2_0p5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 5.5f),
+ // Test case 7: Fold (0.0, 0.0) dot v
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2double Function\n" +
+ "%2 = OpLoad %v2double %v\n" +
+ "%3 = OpDot %double %v2double_0_0 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 8: Fold v dot (0.0, 0.0)
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2double Function\n" +
+ "%2 = OpLoad %v2double %v\n" +
+ "%3 = OpDot %double %2 %v2double_0_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 9: Fold Null dot v
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2double Function\n" +
+ "%2 = OpLoad %v2double %v\n" +
+ "%3 = OpDot %double %v2double_null %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 10: Fold v dot Null
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%v = OpVariable %_ptr_v2double Function\n" +
+ "%2 = OpLoad %v2double %v\n" +
+ "%3 = OpDot %double %2 %v2double_null\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 0.0f),
+ // Test case 11: Fold -2.0
+ InstructionFoldingCase<double>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFNegate %double %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, -2)
+));
+// clang-format on
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(DoubleOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold 1.0 == 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold 1.0 != 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdNotEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold 1.0 < 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThan %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 3: fold 1.0 > 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThan %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 4: fold 1.0 <= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThanEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 5: fold 1.0 >= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 6: fold 1.0 == 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 7: fold 1.0 != 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdNotEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 8: fold 1.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThan %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 9: fold 1.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThan %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 10: fold 1.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThanEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 11: fold 1.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 12: fold 2.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThan %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 13: fold 2.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThan %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 14: fold 2.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThanEqual %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 15: fold 2.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(DoubleUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold 1.0 == 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold 1.0 != 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordNotEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold 1.0 < 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThan %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 3: fold 1.0 > 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThan %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 4: fold 1.0 <= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThanEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 5: fold 1.0 >= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %double_1 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 6: fold 1.0 == 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 7: fold 1.0 != 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordNotEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 8: fold 1.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThan %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 9: fold 1.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThan %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 10: fold 1.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThanEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 11: fold 1.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %double_1 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 12: fold 2.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThan %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 13: fold 2.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThan %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 14: fold 2.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThanEqual %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 15: fold 2.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %double_2 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(FloatOrderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold 1.0 == 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold 1.0 != 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdNotEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold 1.0 < 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThan %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 3: fold 1.0 > 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThan %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 4: fold 1.0 <= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThanEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 5: fold 1.0 >= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 6: fold 1.0 == 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 7: fold 1.0 != 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdNotEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 8: fold 1.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThan %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 9: fold 1.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThan %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 10: fold 1.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThanEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 11: fold 1.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 12: fold 2.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThan %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 13: fold 2.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThan %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 14: fold 2.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdLessThanEqual %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 15: fold 2.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(FloatUnorderedCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold 1.0 == 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold 1.0 != 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordNotEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold 1.0 < 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThan %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 3: fold 1.0 > 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThan %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 4: fold 1.0 <= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThanEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 5: fold 1.0 >= 2.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %float_1 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 6: fold 1.0 == 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 7: fold 1.0 != 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordNotEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 8: fold 1.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThan %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 9: fold 1.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThan %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 10: fold 1.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThanEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 11: fold 1.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %float_1 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 12: fold 2.0 < 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThan %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 13: fold 2.0 > 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThan %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 14: fold 2.0 <= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordLessThanEqual %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 15: fold 2.0 >= 1.0
+ InstructionFoldingCase<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %float_2 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(DoubleNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold NaN == 0 (ord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdEqual %bool %double_nan %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold NaN == NaN (unord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordEqual %bool %double_nan %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold NaN != NaN (ord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdNotEqual %bool %double_nan %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 3: fold NaN != NaN (unord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordNotEqual %bool %double_nan %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(FloatNaNCompareConstantFoldingTest, BooleanInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: fold NaN == 0 (ord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdEqual %bool %float_nan %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 1: fold NaN == NaN (unord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordEqual %bool %float_nan %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: fold NaN != NaN (ord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFOrdNotEqual %bool %float_nan %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, false),
+ // Test case 3: fold NaN != NaN (unord)
+ InstructionFoldingCase<bool>(
+ HeaderWithNaN() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFUnordNotEqual %bool %float_nan %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+// clang-format on
+
+template <class ResultType>
+struct InstructionFoldingCaseWithMap {
+ InstructionFoldingCaseWithMap(const std::string& tb, uint32_t id,
+ ResultType result,
+ std::function<uint32_t(uint32_t)> map)
+ : test_body(tb), id_to_fold(id), expected_result(result), id_map(map) {}
+
+ std::string test_body;
+ uint32_t id_to_fold;
+ ResultType expected_result;
+ std::function<uint32_t(uint32_t)> id_map;
+};
+
+using IntegerInstructionFoldingTestWithMap =
+ ::testing::TestWithParam<InstructionFoldingCaseWithMap<uint32_t>>;
+
+TEST_P(IntegerInstructionFoldingTestWithMap, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ inst = context->get_instruction_folder().FoldInstructionToConstant(inst,
+ tc.id_map);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_NE(inst, nullptr);
+ if (inst != nullptr) {
+ EXPECT_EQ(inst->opcode(), SpvOpConstant);
+ analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+ const analysis::IntConstant* result =
+ const_mrg->GetConstantFromInst(inst)->AsIntConstant();
+ EXPECT_NE(result, nullptr);
+ if (result != nullptr) {
+ EXPECT_EQ(result->GetU32BitValue(), tc.expected_result);
+ }
+ }
+}
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, IntegerInstructionFoldingTestWithMap,
+ ::testing::Values(
+ // Test case 0: fold %3 = 0; %3 * n
+ InstructionFoldingCaseWithMap<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%3 = OpCopyObject %int %int_0\n"
+ "%2 = OpIMul %int %3 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0, [](uint32_t id) {return (id == 3 ? INT_0_ID : id);})
+ ));
+// clang-format on
+
+using BooleanInstructionFoldingTestWithMap =
+ ::testing::TestWithParam<InstructionFoldingCaseWithMap<bool>>;
+
+TEST_P(BooleanInstructionFoldingTestWithMap, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ inst = context->get_instruction_folder().FoldInstructionToConstant(inst,
+ tc.id_map);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_NE(inst, nullptr);
+ if (inst != nullptr) {
+ std::vector<SpvOp> bool_opcodes = {SpvOpConstantTrue, SpvOpConstantFalse};
+ EXPECT_THAT(bool_opcodes, Contains(inst->opcode()));
+ analysis::ConstantManager* const_mrg = context->get_constant_mgr();
+ const analysis::BoolConstant* result =
+ const_mrg->GetConstantFromInst(inst)->AsBoolConstant();
+ EXPECT_NE(result, nullptr);
+ if (result != nullptr) {
+ EXPECT_EQ(result->value(), tc.expected_result);
+ }
+ }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(TestCase, BooleanInstructionFoldingTestWithMap,
+ ::testing::Values(
+ // Test case 0: fold %3 = true; %3 || n
+ InstructionFoldingCaseWithMap<bool>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%3 = OpCopyObject %bool %true\n" +
+ "%2 = OpLogicalOr %bool %3 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true, [](uint32_t id) {return (id == 3 ? TRUE_ID : id);})
+ ));
+// clang-format on
+
+using GeneralInstructionFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<uint32_t>>;
+
+TEST_P(GeneralInstructionFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_EQ(inst->result_id(), original_inst->result_id());
+ EXPECT_EQ(inst->type_id(), original_inst->type_id());
+ EXPECT_TRUE((!succeeded) == (tc.expected_result == 0));
+ if (succeeded) {
+ EXPECT_EQ(inst->opcode(), SpvOpCopyObject);
+ EXPECT_EQ(inst->GetSingleWordInOperand(0), tc.expected_result);
+ } else {
+ EXPECT_EQ(inst->NumInOperands(), original_inst->NumInOperands());
+ for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+ EXPECT_EQ(inst->GetOperand(i), original_inst->GetOperand(i));
+ }
+ }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(IntegerArithmeticTestCases, GeneralInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold n * m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpIMul %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Don't fold n / m (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpUDiv %uint %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 2: Don't fold n / m (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpSDiv %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: Don't fold n remainder m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpSRem %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 4: Don't fold n % m (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpSMod %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 5: Don't fold n % m (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpUMod %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 6: Don't fold n << m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpShiftRightLogical %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 7: Don't fold n >> m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpShiftLeftLogical %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 8: Don't fold n | m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpBitwiseOr %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 9: Don't fold n & m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpBitwiseAnd %int %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 10: Don't fold n < m (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpULessThan %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 11: Don't fold n > m (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpUGreaterThan %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 12: Don't fold n <= m (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpULessThanEqual %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 13: Don't fold n >= m (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%m = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%load_m = OpLoad %uint %m\n" +
+ "%2 = OpUGreaterThanEqual %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 14: Don't fold n < m (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpULessThan %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 15: Don't fold n > m (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpUGreaterThan %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 16: Don't fold n <= m (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpULessThanEqual %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 17: Don't fold n >= m (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%m = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%load_m = OpLoad %int %m\n" +
+ "%2 = OpUGreaterThanEqual %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 18: Don't fold n || m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%m = OpVariable %_ptr_bool Function\n" +
+ "%load_n = OpLoad %bool %n\n" +
+ "%load_m = OpLoad %bool %m\n" +
+ "%2 = OpLogicalOr %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 19: Don't fold n && m
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%m = OpVariable %_ptr_bool Function\n" +
+ "%load_n = OpLoad %bool %n\n" +
+ "%load_m = OpLoad %bool %m\n" +
+ "%2 = OpLogicalAnd %bool %load_n %load_m\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 20: Don't fold n * 3
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpIMul %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 21: Don't fold n / 3 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpUDiv %uint %load_n %uint_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 22: Don't fold n / 3 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpSDiv %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 23: Don't fold n remainder 3
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpSRem %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 24: Don't fold n % 3 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpSMod %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 25: Don't fold n % 3 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpUMod %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 26: Don't fold n << 3
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpShiftRightLogical %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 27: Don't fold n >> 3
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpShiftLeftLogical %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 28: Don't fold n | 3
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpBitwiseOr %int %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 29: Don't fold n & 3
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpBitwiseAnd %uint %load_n %uint_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 30: Don't fold n < 3 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpULessThan %bool %load_n %uint_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 31: Don't fold n > 3 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpUGreaterThan %bool %load_n %uint_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 32: Don't fold n <= 3 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpULessThanEqual %bool %load_n %uint_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 33: Don't fold n >= 3 (unsigned)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%load_n = OpLoad %uint %n\n" +
+ "%2 = OpUGreaterThanEqual %bool %load_n %uint_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 34: Don't fold n < 3 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpULessThan %bool %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 35: Don't fold n > 3 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpUGreaterThan %bool %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 36: Don't fold n <= 3 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpULessThanEqual %bool %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 37: Don't fold n >= 3 (signed)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load_n = OpLoad %int %n\n" +
+ "%2 = OpUGreaterThanEqual %bool %load_n %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 38: Don't fold 2 + 3 (long), bad length
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpIAdd %long %long_2 %long_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 39: Don't fold 2 + 3 (short), bad length
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpIAdd %short %short_2 %short_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 40: fold 1*n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%3 = OpLoad %int %n\n" +
+ "%2 = OpIMul %int %int_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 41: fold n*1
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%3 = OpLoad %int %n\n" +
+ "%2 = OpIMul %int %3 %int_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3)
+));
+
+INSTANTIATE_TEST_CASE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: fold Insert feeding extract
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %n\n" +
+ "%3 = OpCompositeInsert %v4int %2 %v4int_0_0_0_0 0\n" +
+ "%4 = OpCompositeInsert %v4int %int_1 %3 1\n" +
+ "%5 = OpCompositeInsert %v4int %int_1 %4 2\n" +
+ "%6 = OpCompositeInsert %v4int %int_1 %5 3\n" +
+ "%7 = OpCompositeExtract %int %6 0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 7, 2),
+ // Test case 1: fold Composite construct feeding extract (position 0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %n\n" +
+ "%3 = OpCompositeConstruct %v4int %2 %int_0 %int_0 %int_0\n" +
+ "%4 = OpCompositeExtract %int %3 0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, 2),
+ // Test case 2: fold Composite construct feeding extract (position 3)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %n\n" +
+ "%3 = OpCompositeConstruct %v4int %2 %int_0 %int_0 %100\n" +
+ "%4 = OpCompositeExtract %int %3 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, INT_0_ID),
+ // Test case 3: fold Composite construct with vectors feeding extract (scalar element)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %n\n" +
+ "%3 = OpCompositeConstruct %v2int %2 %int_0\n" +
+ "%4 = OpCompositeConstruct %v4int %3 %int_0 %100\n" +
+ "%5 = OpCompositeExtract %int %4 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, INT_0_ID),
+ // Test case 4: fold Composite construct with vectors feeding extract (start of vector element)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %n\n" +
+ "%3 = OpCompositeConstruct %v2int %2 %int_0\n" +
+ "%4 = OpCompositeConstruct %v4int %3 %int_0 %100\n" +
+ "%5 = OpCompositeExtract %int %4 0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, 2),
+ // Test case 5: fold Composite construct with vectors feeding extract (middle of vector element)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %n\n" +
+ "%3 = OpCompositeConstruct %v2int %int_0 %2\n" +
+ "%4 = OpCompositeConstruct %v4int %3 %int_0 %100\n" +
+ "%5 = OpCompositeExtract %int %4 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, 2),
+ // Test case 6: fold Composite construct with multiple indices.
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %n\n" +
+ "%3 = OpCompositeConstruct %v2int %int_0 %2\n" +
+ "%4 = OpCompositeConstruct %struct_v2int_int_int %3 %int_0 %100\n" +
+ "%5 = OpCompositeExtract %int %4 0 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, 2),
+ // Test case 7: fold constant extract.
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpCompositeExtract %int %102 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, INT_7_ID),
+ // Test case 8: constant struct has OpUndef
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpCompositeExtract %int %struct_undef_0_0 0 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 9: Extracting a member of element inserted via Insert
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_struct_v2int_int_int Function\n" +
+ "%2 = OpLoad %struct_v2int_int_int %n\n" +
+ "%3 = OpCompositeInsert %struct_v2int_int_int %102 %2 0\n" +
+ "%4 = OpCompositeExtract %int %3 0 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, 103),
+ // Test case 10: Extracting a element that is partially changed by Insert. (Don't fold)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_struct_v2int_int_int Function\n" +
+ "%2 = OpLoad %struct_v2int_int_int %n\n" +
+ "%3 = OpCompositeInsert %struct_v2int_int_int %int_0 %2 0 1\n" +
+ "%4 = OpCompositeExtract %v2int %3 0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, 0),
+ // Test case 11: Extracting from result of vector shuffle (first input)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%2 = OpLoad %v2int %n\n" +
+ "%3 = OpVectorShuffle %v2int %102 %2 3 0\n" +
+ "%4 = OpCompositeExtract %int %3 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, INT_7_ID),
+ // Test case 12: Extracting from result of vector shuffle (second input)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%2 = OpLoad %v2int %n\n" +
+ "%3 = OpVectorShuffle %v2int %2 %102 2 0\n" +
+ "%4 = OpCompositeExtract %int %3 0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, INT_7_ID)
+));
+
+INSTANTIATE_TEST_CASE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: fold Extracts feeding construct
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpCopyObject %v4int %v4int_0_0_0_0\n" +
+ "%3 = OpCompositeExtract %int %2 0\n" +
+ "%4 = OpCompositeExtract %int %2 1\n" +
+ "%5 = OpCompositeExtract %int %2 2\n" +
+ "%6 = OpCompositeExtract %int %2 3\n" +
+ "%7 = OpCompositeConstruct %v4int %3 %4 %5 %6\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 7, 2),
+ // Test case 1: Don't fold Extracts feeding construct (Different source)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpCopyObject %v4int %v4int_0_0_0_0\n" +
+ "%3 = OpCompositeExtract %int %2 0\n" +
+ "%4 = OpCompositeExtract %int %2 1\n" +
+ "%5 = OpCompositeExtract %int %2 2\n" +
+ "%6 = OpCompositeExtract %int %v4int_0_0_0_0 3\n" +
+ "%7 = OpCompositeConstruct %v4int %3 %4 %5 %6\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 7, 0),
+ // Test case 2: Don't fold Extracts feeding construct (bad indices)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpCopyObject %v4int %v4int_0_0_0_0\n" +
+ "%3 = OpCompositeExtract %int %2 0\n" +
+ "%4 = OpCompositeExtract %int %2 0\n" +
+ "%5 = OpCompositeExtract %int %2 2\n" +
+ "%6 = OpCompositeExtract %int %2 3\n" +
+ "%7 = OpCompositeConstruct %v4int %3 %4 %5 %6\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 7, 0),
+ // Test case 3: Don't fold Extracts feeding construct (different type)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpCopyObject %struct_v2int_int_int %struct_v2int_int_int_null\n" +
+ "%3 = OpCompositeExtract %v2int %2 0\n" +
+ "%4 = OpCompositeExtract %int %2 1\n" +
+ "%5 = OpCompositeExtract %int %2 2\n" +
+ "%7 = OpCompositeConstruct %v4int %3 %4 %5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 7, 0),
+ // Test case 4: Fold construct with constants to constant.
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpCompositeConstruct %v2int %103 %103\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, VEC2_0_ID)
+));
+
+INSTANTIATE_TEST_CASE_P(PhiFoldingTest, GeneralInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Fold phi with the same values for all edges.
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ " OpBranchConditional %true %l1 %l2\n" +
+ "%l1 = OpLabel\n" +
+ " OpBranch %merge_lab\n" +
+ "%l2 = OpLabel\n" +
+ " OpBranch %merge_lab\n" +
+ "%merge_lab = OpLabel\n" +
+ "%2 = OpPhi %int %100 %l1 %100 %l2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, INT_0_ID),
+ // Test case 1: Fold phi in pass through loop.
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ " OpBranch %l1\n" +
+ "%l1 = OpLabel\n" +
+ "%2 = OpPhi %int %100 %main_lab %2 %l1\n" +
+ " OpBranchConditional %true %l1 %merge_lab\n" +
+ "%merge_lab = OpLabel\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, INT_0_ID),
+ // Test case 2: Don't Fold phi because of different values.
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ " OpBranch %l1\n" +
+ "%l1 = OpLabel\n" +
+ "%2 = OpPhi %int %int_0 %main_lab %int_3 %l1\n" +
+ " OpBranchConditional %true %l1 %merge_lab\n" +
+ "%merge_lab = OpLabel\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0)
+));
+
+INSTANTIATE_TEST_CASE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold n + 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFAdd %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Don't fold n - 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFSub %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 2: Don't fold n * 2.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFMul %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: Fold n + 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFAdd %float %3 %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 4: Fold 0.0 + n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFAdd %float %float_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 5: Fold n - 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFSub %float %3 %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 6: Fold n * 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFMul %float %3 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 7: Fold 1.0 * n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFMul %float %float_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 8: Fold n / 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFDiv %float %3 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 9: Fold n * 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFMul %float %3 %104\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, FLOAT_0_ID),
+ // Test case 10: Fold 0.0 * n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFMul %float %104 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, FLOAT_0_ID),
+ // Test case 11: Fold 0.0 / n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFDiv %float %104 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, FLOAT_0_ID),
+ // Test case 12: Don't fold mix(a, b, 2.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_float Function\n" +
+ "%b = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %a\n" +
+ "%4 = OpLoad %float %b\n" +
+ "%2 = OpExtInst %float %1 FMix %3 %4 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 13: Fold mix(a, b, 0.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_float Function\n" +
+ "%b = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %a\n" +
+ "%4 = OpLoad %float %b\n" +
+ "%2 = OpExtInst %float %1 FMix %3 %4 %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 14: Fold mix(a, b, 1.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_float Function\n" +
+ "%b = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %a\n" +
+ "%4 = OpLoad %float %b\n" +
+ "%2 = OpExtInst %float %1 FMix %3 %4 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 4),
+ // Test case 15: Fold vector fadd with null
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %a\n" +
+ "%3 = OpFAdd %v2float %2 %v2float_null\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 2),
+ // Test case 16: Fold vector fadd with null
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %a\n" +
+ "%3 = OpFAdd %v2float %v2float_null %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 2),
+ // Test case 15: Fold vector fsub with null
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %a\n" +
+ "%3 = OpFSub %v2float %2 %v2float_null\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, 2),
+ // Test case 16: Fold 0.0(half) * n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_half Function\n" +
+ "%3 = OpLoad %half %n\n" +
+ "%2 = OpFMul %half %108 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, HALF_0_ID),
+ // Test case 17: Don't fold 1.0(half) * n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_half Function\n" +
+ "%3 = OpLoad %half %n\n" +
+ "%2 = OpFMul %half %half_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 18: Don't fold 1.0 * 1.0 (half)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFMul %half %half_1 %half_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0)
+));
+
+INSTANTIATE_TEST_CASE_P(DoubleRedundantFoldingTest, GeneralInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold n + 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFAdd %double %3 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Don't fold n - 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFSub %double %3 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 2: Don't fold n * 2.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFMul %double %3 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: Fold n + 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFAdd %double %3 %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 4: Fold 0.0 + n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFAdd %double %double_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 5: Fold n - 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFSub %double %3 %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 6: Fold n * 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFMul %double %3 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 7: Fold 1.0 * n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFMul %double %double_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 8: Fold n / 1.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFDiv %double %3 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 9: Fold n * 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFMul %double %3 %105\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, DOUBLE_0_ID),
+ // Test case 10: Fold 0.0 * n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFMul %double %105 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, DOUBLE_0_ID),
+ // Test case 11: Fold 0.0 / n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFDiv %double %105 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, DOUBLE_0_ID),
+ // Test case 12: Don't fold mix(a, b, 2.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_double Function\n" +
+ "%b = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %a\n" +
+ "%4 = OpLoad %double %b\n" +
+ "%2 = OpExtInst %double %1 FMix %3 %4 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 13: Fold mix(a, b, 0.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_double Function\n" +
+ "%b = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %a\n" +
+ "%4 = OpLoad %double %b\n" +
+ "%2 = OpExtInst %double %1 FMix %3 %4 %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 14: Fold mix(a, b, 1.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%a = OpVariable %_ptr_double Function\n" +
+ "%b = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %a\n" +
+ "%4 = OpLoad %double %b\n" +
+ "%2 = OpExtInst %double %1 FMix %3 %4 %double_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 4)
+));
+
+INSTANTIATE_TEST_CASE_P(FloatVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%3 = OpLoad %v4float %n\n" +
+ "%2 = OpFMul %v4float %3 %v4float_0_0_0_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Fold a * vec4(0.0, 0.0, 0.0, 0.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%3 = OpLoad %v4float %n\n" +
+ "%2 = OpFMul %v4float %3 %106\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, VEC4_0_ID),
+ // Test case 2: Fold a * vec4(1.0, 1.0, 1.0, 1.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%3 = OpLoad %v4float %n\n" +
+ "%2 = OpFMul %v4float %3 %v4float_1_1_1_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3)
+));
+
+INSTANTIATE_TEST_CASE_P(DoubleVectorRedundantFoldingTest, GeneralInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold a * vec4(0.0, 0.0, 0.0, 1.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%2 = OpFMul %v4double %3 %v4double_0_0_0_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Fold a * vec4(0.0, 0.0, 0.0, 0.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%2 = OpFMul %v4double %3 %106\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, DVEC4_0_ID),
+ // Test case 2: Fold a * vec4(1.0, 1.0, 1.0, 1.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%2 = OpFMul %v4double %3 %v4double_1_1_1_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3)
+));
+
+INSTANTIATE_TEST_CASE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold n + 1
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%3 = OpLoad %uint %n\n" +
+ "%2 = OpIAdd %uint %3 %uint_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Don't fold 1 + n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%3 = OpLoad %uint %n\n" +
+ "%2 = OpIAdd %uint %uint_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 2: Fold n + 0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%3 = OpLoad %uint %n\n" +
+ "%2 = OpIAdd %uint %3 %uint_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 3: Fold 0 + n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_uint Function\n" +
+ "%3 = OpLoad %uint %n\n" +
+ "%2 = OpIAdd %uint %uint_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 4: Don't fold n + (1,0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%3 = OpLoad %v2int %n\n" +
+ "%2 = OpIAdd %v2int %3 %v2int_1_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 5: Don't fold (1,0) + n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%3 = OpLoad %v2int %n\n" +
+ "%2 = OpIAdd %v2int %v2int_1_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 6: Fold n + (0,0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%3 = OpLoad %v2int %n\n" +
+ "%2 = OpIAdd %v2int %3 %v2int_0_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 7: Fold (0,0) + n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%3 = OpLoad %v2int %n\n" +
+ "%2 = OpIAdd %v2int %v2int_0_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3)
+));
+
+INSTANTIATE_TEST_CASE_P(ClampAndCmpLHS, GeneralInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Don't Fold 0.0 < clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Don't Fold 0.0 < clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 2: Don't Fold 0.0 <= clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordLessThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: Don't Fold 0.0 <= clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdLessThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 4: Don't Fold 0.0 > clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 5: Don't Fold 0.0 > clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 6: Don't Fold 0.0 >= clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 7: Don't Fold 0.0 >= clamp(-1, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 8: Don't Fold 0.0 < clamp(0, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFUnordLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 9: Don't Fold 0.0 < clamp(0, 1)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFOrdLessThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 10: Don't Fold 0.0 > clamp(-1, 0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFUnordGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 11: Don't Fold 0.0 > clamp(-1, 0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFOrdGreaterThan %bool %float_0 %clamp\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0)
+));
+
+INSTANTIATE_TEST_CASE_P(ClampAndCmpRHS, GeneralInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Don't Fold clamp(-1, 1) < 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordLessThan %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Don't Fold clamp(-1, 1) < 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdLessThan %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 2: Don't Fold clamp(-1, 1) <= 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordLessThanEqual %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: Don't Fold clamp(-1, 1) <= 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdLessThanEqual %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 4: Don't Fold clamp(-1, 1) > 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordGreaterThan %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 5: Don't Fold clamp(-1, 1) > 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdGreaterThan %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 6: Don't Fold clamp(-1, 1) >= 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFUnordGreaterThanEqual %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 7: Don't Fold clamp(-1, 1) >= 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_1\n" +
+ "%2 = OpFOrdGreaterThanEqual %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 8: Don't Fold clamp(-1, 0) < 0.0
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFUnordLessThan %bool %clamp %float_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 9: Don't Fold clamp(0, 1) < 1
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_0 %float_1\n" +
+ "%2 = OpFOrdLessThan %bool %clamp %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 10: Don't Fold clamp(-1, 0) > -1
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFUnordGreaterThan %bool %clamp %float_n1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 11: Don't Fold clamp(-1, 0) > -1
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%ld = OpLoad %float %n\n" +
+ "%clamp = OpExtInst %float %1 FClamp %ld %float_n1 %float_0\n" +
+ "%2 = OpFOrdGreaterThan %bool %clamp %float_n1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0)
+));
+
+INSTANTIATE_TEST_CASE_P(FToIConstantFoldingTest, IntegerInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Fold int(3.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpConvertFToS %int %float_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 1: Fold uint(3.0)
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpConvertFToU %int %float_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3)
+));
+
+INSTANTIATE_TEST_CASE_P(IToFConstantFoldingTest, FloatInstructionFoldingTest,
+ ::testing::Values(
+ // Test case 0: Fold float(3)
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpConvertSToF %float %int_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3.0),
+ // Test case 1: Fold float(3u)
+ InstructionFoldingCase<float>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpConvertUToF %float %uint_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3.0)
+));
+// clang-format on
+
+using ToNegateFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<uint32_t>>;
+
+TEST_P(ToNegateFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+
+ // Make sure the instruction folded as expected.
+ EXPECT_EQ(inst->result_id(), original_inst->result_id());
+ EXPECT_EQ(inst->type_id(), original_inst->type_id());
+ EXPECT_TRUE((!succeeded) == (tc.expected_result == 0));
+ if (succeeded) {
+ EXPECT_EQ(inst->opcode(), SpvOpFNegate);
+ EXPECT_EQ(inst->GetSingleWordInOperand(0), tc.expected_result);
+ } else {
+ EXPECT_EQ(inst->NumInOperands(), original_inst->NumInOperands());
+ for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
+ EXPECT_EQ(inst->GetOperand(i), original_inst->GetOperand(i));
+ }
+ }
+}
+
+// clang-format off
+INSTANTIATE_TEST_CASE_P(FloatRedundantSubFoldingTest, ToNegateFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold 1.0 - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFSub %float %float_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Fold 0.0 - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_float Function\n" +
+ "%3 = OpLoad %float %n\n" +
+ "%2 = OpFSub %float %float_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 2: Don't fold (0,0,0,1) - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%3 = OpLoad %v4float %n\n" +
+ "%2 = OpFSub %v4float %v4float_0_0_0_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: Fold (0,0,0,0) - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%3 = OpLoad %v4float %n\n" +
+ "%2 = OpFSub %v4float %v4float_0_0_0_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3)
+));
+
+INSTANTIATE_TEST_CASE_P(DoubleRedundantSubFoldingTest, ToNegateFoldingTest,
+ ::testing::Values(
+ // Test case 0: Don't fold 1.0 - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFSub %double %double_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 1: Fold 0.0 - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%3 = OpLoad %double %n\n" +
+ "%2 = OpFSub %double %double_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3),
+ // Test case 2: Don't fold (0,0,0,1) - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%2 = OpFSub %v4double %v4double_0_0_0_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 0),
+ // Test case 3: Fold (0,0,0,0) - n
+ InstructionFoldingCase<uint32_t>(
+ Header() + "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%2 = OpFSub %v4double %v4double_0_0_0_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, 3)
+));
+
+using MatchingInstructionFoldingTest =
+ ::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+TEST_P(MatchingInstructionFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
+ Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
+ std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+ EXPECT_EQ(succeeded, tc.expected_result);
+ if (succeeded) {
+ Match(tc.test_body, context.get());
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(RedundantIntegerMatching, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Fold 0 + n (change sign)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[uint:%\\w+]] = OpTypeInt 32 0\n" +
+ "; CHECK: %2 = OpBitcast [[uint]] %3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%3 = OpLoad %uint %n\n" +
+ "%2 = OpIAdd %uint %int_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 2, true),
+ // Test case 0: Fold 0 + n (change sign)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: %2 = OpBitcast [[int]] %3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%3 = OpLoad %int %n\n" +
+ "%2 = OpIAdd %int %uint_0 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(MergeNegateTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: fold consecutive fnegate
+ // -(-x) = x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float:%\\w+]]\n" +
+ "; CHECK: %4 = OpCopyObject [[float]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFNegate %float %2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 1: fold fnegate(fmul with const).
+ // -(x * 2.0) = x * -2.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFMul %float %2 %float_2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 2: fold fnegate(fmul with const).
+ // -(2.0 * x) = x * 2.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFMul %float %float_2 %2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 3: fold fnegate(fdiv with const).
+ // -(x / 2.0) = x * -0.5
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n0p5:%\\w+]] = OpConstant [[float]] -0.5\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_n0p5]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %2 %float_2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 4: fold fnegate(fdiv with const).
+ // -(2.0 / x) = -2.0 / x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFDiv [[float]] [[float_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %float_2 %2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 5: fold fnegate(fadd with const).
+ // -(2.0 + x) = -2.0 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %float_2 %2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 6: fold fnegate(fadd with const).
+ // -(x + 2.0) = -2.0 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %2 %float_2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 7: fold fnegate(fsub with const).
+ // -(2.0 - x) = x - 2.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[ld]] [[float_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %float_2 %2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 8: fold fnegate(fsub with const).
+ // -(x - 2.0) = 2.0 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %2 %float_2\n" +
+ "%4 = OpFNegate %float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 9: fold consecutive snegate
+ // -(-x) = x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int:%\\w+]]\n" +
+ "; CHECK: %4 = OpCopyObject [[int]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSNegate %int %2\n" +
+ "%4 = OpSNegate %int %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 10: fold consecutive vector negate
+ // -(-x) = x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float:%\\w+]]\n" +
+ "; CHECK: %4 = OpCopyObject [[v2float]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %var\n" +
+ "%3 = OpFNegate %v2float %2\n" +
+ "%4 = OpFNegate %v2float %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 11: fold snegate(iadd with const).
+ // -(2 + x) = -2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: OpConstant [[int]] -2147483648\n" +
+ "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+ "; CHECK: %4 = OpISub [[int]] [[int_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpIAdd %int %int_2 %2\n" +
+ "%4 = OpSNegate %int %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 12: fold snegate(iadd with const).
+ // -(x + 2) = -2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: OpConstant [[int]] -2147483648\n" +
+ "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+ "; CHECK: %4 = OpISub [[int]] [[int_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpIAdd %int %2 %int_2\n" +
+ "%4 = OpSNegate %int %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 13: fold snegate(isub with const).
+ // -(2 - x) = x - 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[int_2:%\\w+]] = OpConstant [[int]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+ "; CHECK: %4 = OpISub [[int]] [[ld]] [[int_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpISub %int %int_2 %2\n" +
+ "%4 = OpSNegate %int %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 14: fold snegate(isub with const).
+ // -(x - 2) = 2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[int_2:%\\w+]] = OpConstant [[int]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+ "; CHECK: %4 = OpISub [[int]] [[int_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpISub %int %2 %int_2\n" +
+ "%4 = OpSNegate %int %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 15: fold snegate(iadd with const).
+ // -(x + 2) = -2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
+ "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpISub [[long]] [[long_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpIAdd %long %2 %long_2\n" +
+ "%4 = OpSNegate %long %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 16: fold snegate(isub with const).
+ // -(2 - x) = x - 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
+ "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpISub [[long]] [[ld]] [[long_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpISub %long %long_2 %2\n" +
+ "%4 = OpSNegate %long %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 17: fold snegate(isub with const).
+ // -(x - 2) = 2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
+ "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpISub [[long]] [[long_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpISub %long %2 %long_2\n" +
+ "%4 = OpSNegate %long %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 18: fold -vec4(-1.0, 2.0, 1.0, 3.0)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[v4float:%\\w+]] = OpTypeVector [[float]] 4\n" +
+ "; CHECK: [[float_n1:%\\w+]] = OpConstant [[float]] -1\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+ "; CHECK: [[float_n3:%\\w+]] = OpConstant [[float]] -3\n" +
+ "; CHECK: [[v4float_1_n2_n1_n3:%\\w+]] = OpConstantComposite [[v4float]] [[float_1]] [[float_n2]] [[float_n1]] [[float_n3]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v4float]] [[v4float_1_n2_n1_n3]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFNegate %v4float %v4float_n1_2_1_3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 19: fold vector fnegate with null
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v2double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: [[double_n0:%\\w+]] = OpConstant [[double]] -0\n" +
+ "; CHECK: [[v2double_0_0:%\\w+]] = OpConstantComposite [[v2double]] [[double_n0]] [[double_n0]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v2double]] [[v2double_0_0]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpFNegate %v2double %v2double_null\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true)
+));
+
+INSTANTIATE_TEST_CASE_P(ReciprocalFDivTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: scalar reicprocal
+ // x / 0.5 = x * 2.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %3 = OpFMul [[float]] [[ld]] [[float_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %2 %float_0p5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 3, true),
+ // Test case 1: Unfoldable
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_0:%\\w+]] = OpConstant [[float]] 0\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %3 = OpFDiv [[float]] [[ld]] [[float_0]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %2 %104\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 3, false),
+ // Test case 2: Vector reciprocal
+ // x / {2.0, 0.5} = x * {0.5, 2.0}
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[float_0p5:%\\w+]] = OpConstant [[float]] 0.5\n" +
+ "; CHECK: [[v2float_0p5_2:%\\w+]] = OpConstantComposite [[v2float]] [[float_0p5]] [[float_2]]\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float]]\n" +
+ "; CHECK: %3 = OpFMul [[v2float]] [[ld]] [[v2float_0p5_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %var\n" +
+ "%3 = OpFDiv %v2float %2 %v2float_2_0p5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 3, true),
+ // Test case 3: double reciprocal
+ // x / 2.0 = x * 0.5
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[double_0p5:%\\w+]] = OpConstant [[double]] 0.5\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[double]]\n" +
+ "; CHECK: %3 = OpFMul [[double]] [[ld]] [[double_0p5]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_double Function\n" +
+ "%2 = OpLoad %double %var\n" +
+ "%3 = OpFDiv %double %2 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 3, true),
+ // Test case 4: don't fold x / 0.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %var\n" +
+ "%3 = OpFDiv %v2float %2 %v2float_null\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 3, false)
+));
+
+INSTANTIATE_TEST_CASE_P(MergeMulTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: fold consecutive fmuls
+ // (x * 3.0) * 2.0 = x * 6.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFMul %float %2 %float_3\n" +
+ "%4 = OpFMul %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 1: fold consecutive fmuls
+ // 2.0 * (x * 3.0) = x * 6.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFMul %float %2 %float_3\n" +
+ "%4 = OpFMul %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 2: fold consecutive fmuls
+ // (3.0 * x) * 2.0 = x * 6.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFMul [[float]] [[ld]] [[float_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFMul %float %float_3 %2\n" +
+ "%4 = OpFMul %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 3: fold vector fmul
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" +
+ "; CHECK: [[float_6:%\\w+]] = OpConstant [[float]] 6\n" +
+ "; CHECK: [[v2float_6_6:%\\w+]] = OpConstantComposite [[v2float]] [[float_6]] [[float_6]]\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float]]\n" +
+ "; CHECK: %4 = OpFMul [[v2float]] [[ld]] [[v2float_6_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %var\n" +
+ "%3 = OpFMul %v2float %2 %v2float_2_3\n" +
+ "%4 = OpFMul %v2float %3 %v2float_3_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 4: fold double fmuls
+ // (x * 3.0) * 2.0 = x * 6.0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[double_6:%\\w+]] = OpConstant [[double]] 6\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[double]]\n" +
+ "; CHECK: %4 = OpFMul [[double]] [[ld]] [[double_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_double Function\n" +
+ "%2 = OpLoad %double %var\n" +
+ "%3 = OpFMul %double %2 %double_3\n" +
+ "%4 = OpFMul %double %3 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 5: fold 32 bit imuls
+ // (x * 3) * 2 = x * 6
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[int_6:%\\w+]] = OpConstant [[int]] 6\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+ "; CHECK: %4 = OpIMul [[int]] [[ld]] [[int_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpIMul %int %2 %int_3\n" +
+ "%4 = OpIMul %int %3 %int_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 6: fold 64 bit imuls
+ // (x * 3) * 2 = x * 6
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64\n" +
+ "; CHECK: [[long_6:%\\w+]] = OpConstant [[long]] 6\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpIMul [[long]] [[ld]] [[long_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpIMul %long %2 %long_3\n" +
+ "%4 = OpIMul %long %3 %long_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 7: merge vector integer mults
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
+ "; CHECK: [[int_6:%\\w+]] = OpConstant [[int]] 6\n" +
+ "; CHECK: [[v2int_6_6:%\\w+]] = OpConstantComposite [[v2int]] [[int_6]] [[int_6]]\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" +
+ "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_6_6]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2int Function\n" +
+ "%2 = OpLoad %v2int %var\n" +
+ "%3 = OpIMul %v2int %2 %v2int_2_3\n" +
+ "%4 = OpIMul %v2int %3 %v2int_3_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 8: merge fmul of fdiv
+ // 2.0 * (2.0 / x) = 4.0 / x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_4:%\\w+]] = OpConstant [[float]] 4\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFDiv [[float]] [[float_4]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %float_2 %2\n" +
+ "%4 = OpFMul %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 9: merge fmul of fdiv
+ // (2.0 / x) * 2.0 = 4.0 / x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_4:%\\w+]] = OpConstant [[float]] 4\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFDiv [[float]] [[float_4]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %float_2 %2\n" +
+ "%4 = OpFMul %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 10: Do not merge imul of sdiv
+ // 4 * (x / 2)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSDiv %int %2 %int_2\n" +
+ "%4 = OpIMul %int %int_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 11: Do not merge imul of sdiv
+ // (x / 2) * 4
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSDiv %int %2 %int_2\n" +
+ "%4 = OpIMul %int %3 %int_4\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 12: Do not merge imul of udiv
+ // 4 * (x / 2)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_uint Function\n" +
+ "%2 = OpLoad %uint %var\n" +
+ "%3 = OpUDiv %uint %2 %uint_2\n" +
+ "%4 = OpIMul %uint %uint_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 13: Do not merge imul of udiv
+ // (x / 2) * 4
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_uint Function\n" +
+ "%2 = OpLoad %uint %var\n" +
+ "%3 = OpUDiv %uint %2 %uint_2\n" +
+ "%4 = OpIMul %uint %3 %uint_4\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 14: Don't fold
+ // (x / 3) * 4
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_uint Function\n" +
+ "%2 = OpLoad %uint %var\n" +
+ "%3 = OpUDiv %uint %2 %uint_3\n" +
+ "%4 = OpIMul %uint %3 %uint_4\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 15: merge vector fmul of fdiv
+ // (x / {2,2}) * {4,4} = x * {2,2}
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[v2float_2_2:%\\w+]] = OpConstantComposite [[v2float]] [[float_2]] [[float_2]]\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v2float]]\n" +
+ "; CHECK: %4 = OpFMul [[v2float]] [[ld]] [[v2float_2_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %v2float %var\n" +
+ "%3 = OpFDiv %v2float %2 %v2float_2_2\n" +
+ "%4 = OpFMul %v2float %3 %v2float_4_4\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 16: merge vector imul of snegate
+ // (-x) * {2,2} = x * {-2,-2}
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
+ "; CHECK: OpConstant [[int]] -2147483648\n" +
+ "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+ "; CHECK: [[v2int_n2_n2:%\\w+]] = OpConstantComposite [[v2int]] [[int_n2]] [[int_n2]]\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" +
+ "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_n2_n2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2int Function\n" +
+ "%2 = OpLoad %v2int %var\n" +
+ "%3 = OpSNegate %v2int %2\n" +
+ "%4 = OpIMul %v2int %3 %v2int_2_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 17: merge vector imul of snegate
+ // {2,2} * (-x) = x * {-2,-2}
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
+ "; CHECK: OpConstant [[int]] -2147483648\n" +
+ "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+ "; CHECK: [[v2int_n2_n2:%\\w+]] = OpConstantComposite [[v2int]] [[int_n2]] [[int_n2]]\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v2int]]\n" +
+ "; CHECK: %4 = OpIMul [[v2int]] [[ld]] [[v2int_n2_n2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2int Function\n" +
+ "%2 = OpLoad %v2int %var\n" +
+ "%3 = OpSNegate %v2int %2\n" +
+ "%4 = OpIMul %v2int %v2int_2_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 18: Fold OpVectorTimesScalar
+ // {4,4} = OpVectorTimesScalar v2float {2,2} 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" +
+ "; CHECK: [[float_4:%\\w+]] = OpConstant [[float]] 4\n" +
+ "; CHECK: [[v2float_4_4:%\\w+]] = OpConstantComposite [[v2float]] [[float_4]] [[float_4]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v2float]] [[v2float_4_4]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVectorTimesScalar %v2float %v2float_2_2 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 19: Fold OpVectorTimesScalar
+ // {0,0} = OpVectorTimesScalar v2float v2float_null -1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[v2float:%\\w+]] = OpTypeVector [[float]] 2\n" +
+ "; CHECK: [[v2float_null:%\\w+]] = OpConstantNull [[v2float]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v2float]] [[v2float_null]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVectorTimesScalar %v2float %v2float_null %float_n1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 20: Fold OpVectorTimesScalar
+ // {4,4} = OpVectorTimesScalar v2double {2,2} 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v2double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: [[double_4:%\\w+]] = OpConstant [[double]] 4\n" +
+ "; CHECK: [[v2double_4_4:%\\w+]] = OpConstantComposite [[v2double]] [[double_4]] [[double_4]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v2double]] [[v2double_4_4]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVectorTimesScalar %v2double %v2double_2_2 %double_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 21: Fold OpVectorTimesScalar
+ // {0,0} = OpVectorTimesScalar v2double {0,0} n
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v2double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: {{%\\w+}} = OpConstant [[double]] 0\n" +
+ "; CHECK: [[double_0:%\\w+]] = OpConstant [[double]] 0\n" +
+ "; CHECK: [[v2double_0_0:%\\w+]] = OpConstantComposite [[v2double]] [[double_0]] [[double_0]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v2double]] [[v2double_0_0]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_double Function\n" +
+ "%load = OpLoad %double %n\n" +
+ "%2 = OpVectorTimesScalar %v2double %v2double_0_0 %load\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 22: Fold OpVectorTimesScalar
+ // {0,0} = OpVectorTimesScalar v2double n 0
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v2double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: [[v2double_null:%\\w+]] = OpConstantNull [[v2double]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v2double]] [[v2double_null]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2double Function\n" +
+ "%load = OpLoad %v2double %n\n" +
+ "%2 = OpVectorTimesScalar %v2double %load %double_0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 23: merge fmul of fdiv
+ // x * (y / x) = y
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[ldx:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: [[ldy:%\\w+]] = OpLoad [[float]] [[y:%\\w+]]\n" +
+ "; CHECK: %5 = OpCopyObject [[float]] [[ldy]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%x = OpVariable %_ptr_float Function\n" +
+ "%y = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %x\n" +
+ "%3 = OpLoad %float %y\n" +
+ "%4 = OpFDiv %float %3 %2\n" +
+ "%5 = OpFMul %float %2 %4\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 5, true),
+ // Test case 24: merge fmul of fdiv
+ // (y / x) * x = y
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[ldx:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: [[ldy:%\\w+]] = OpLoad [[float]] [[y:%\\w+]]\n" +
+ "; CHECK: %5 = OpCopyObject [[float]] [[ldy]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%x = OpVariable %_ptr_float Function\n" +
+ "%y = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %x\n" +
+ "%3 = OpLoad %float %y\n" +
+ "%4 = OpFDiv %float %3 %2\n" +
+ "%5 = OpFMul %float %4 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 5, true)
+));
+
+INSTANTIATE_TEST_CASE_P(MergeDivTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: merge consecutive fdiv
+ // 4.0 / (2.0 / x) = 2.0 * x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFMul [[float]] [[float_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %float_2 %2\n" +
+ "%4 = OpFDiv %float %float_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 1: merge consecutive fdiv
+ // 4.0 / (x / 2.0) = 8.0 / x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_8:%\\w+]] = OpConstant [[float]] 8\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFDiv [[float]] [[float_8]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %2 %float_2\n" +
+ "%4 = OpFDiv %float %float_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 2: merge consecutive fdiv
+ // (4.0 / x) / 2.0 = 2.0 / x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFDiv [[float]] [[float_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %float_4 %2\n" +
+ "%4 = OpFDiv %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 3: Do not merge consecutive sdiv
+ // 4 / (2 / x)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSDiv %int %int_2 %2\n" +
+ "%4 = OpSDiv %int %int_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 4: Do not merge consecutive sdiv
+ // 4 / (x / 2)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSDiv %int %2 %int_2\n" +
+ "%4 = OpSDiv %int %int_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 5: Do not merge consecutive sdiv
+ // (4 / x) / 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSDiv %int %int_4 %2\n" +
+ "%4 = OpSDiv %int %3 %int_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 6: Do not merge consecutive sdiv
+ // (x / 4) / 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSDiv %int %2 %int_4\n" +
+ "%4 = OpSDiv %int %3 %int_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 7: Do not merge sdiv of imul
+ // 4 / (2 * x)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpIMul %int %int_2 %2\n" +
+ "%4 = OpSDiv %int %int_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 8: Do not merge sdiv of imul
+ // 4 / (x * 2)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpIMul %int %2 %int_2\n" +
+ "%4 = OpSDiv %int %int_4 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 9: Do not merge sdiv of imul
+ // (4 * x) / 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpIMul %int %int_4 %2\n" +
+ "%4 = OpSDiv %int %3 %int_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 10: Do not merge sdiv of imul
+ // (x * 4) / 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpIMul %int %2 %int_4\n" +
+ "%4 = OpSDiv %int %3 %int_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 11: merge sdiv of snegate
+ // (-x) / 2 = x / -2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: OpConstant [[int]] -2147483648\n" +
+ "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+ "; CHECK: %4 = OpSDiv [[int]] [[ld]] [[int_n2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSNegate %int %2\n" +
+ "%4 = OpSDiv %int %3 %int_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 12: merge sdiv of snegate
+ // 2 / (-x) = -2 / x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: OpConstant [[int]] -2147483648\n" +
+ "; CHECK: [[int_n2:%\\w+]] = OpConstant [[int]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[int]]\n" +
+ "; CHECK: %4 = OpSDiv [[int]] [[int_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_int Function\n" +
+ "%2 = OpLoad %int %var\n" +
+ "%3 = OpSNegate %int %2\n" +
+ "%4 = OpSDiv %int %int_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 13: Don't merge
+ // (x / {null}) / {null}
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_v2float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFDiv %float %2 %v2float_null\n" +
+ "%4 = OpFDiv %float %3 %v2float_null\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, false),
+ // Test case 14: merge fmul of fdiv
+ // (y * x) / x = y
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[ldx:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: [[ldy:%\\w+]] = OpLoad [[float]] [[y:%\\w+]]\n" +
+ "; CHECK: %5 = OpCopyObject [[float]] [[ldy]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%x = OpVariable %_ptr_float Function\n" +
+ "%y = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %x\n" +
+ "%3 = OpLoad %float %y\n" +
+ "%4 = OpFMul %float %3 %2\n" +
+ "%5 = OpFDiv %float %4 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 5, true),
+ // Test case 15: merge fmul of fdiv
+ // (x * y) / x = y
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[ldx:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: [[ldy:%\\w+]] = OpLoad [[float]] [[y:%\\w+]]\n" +
+ "; CHECK: %5 = OpCopyObject [[float]] [[ldy]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%x = OpVariable %_ptr_float Function\n" +
+ "%y = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %x\n" +
+ "%3 = OpLoad %float %y\n" +
+ "%4 = OpFMul %float %2 %3\n" +
+ "%5 = OpFDiv %float %4 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 5, true)
+));
+
+INSTANTIATE_TEST_CASE_P(MergeAddTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: merge add of negate
+ // (-x) + 2 = 2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFNegate %float %2\n" +
+ "%4 = OpFAdd %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 1: merge add of negate
+ // 2 + (-x) = 2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpSNegate %float %2\n" +
+ "%4 = OpIAdd %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 2: merge add of negate
+ // (-x) + 2 = 2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
+ "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpISub [[long]] [[long_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpSNegate %long %2\n" +
+ "%4 = OpIAdd %long %3 %long_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 3: merge add of negate
+ // 2 + (-x) = 2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
+ "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpISub [[long]] [[long_2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpSNegate %long %2\n" +
+ "%4 = OpIAdd %long %long_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 4: merge add of subtract
+ // (x - 1) + 2 = x + 1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %2 %float_1\n" +
+ "%4 = OpFAdd %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 5: merge add of subtract
+ // (1 - x) + 2 = 3 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_3]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %float_1 %2\n" +
+ "%4 = OpFAdd %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 6: merge add of subtract
+ // 2 + (x - 1) = x + 1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %2 %float_1\n" +
+ "%4 = OpFAdd %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 7: merge add of subtract
+ // 2 + (1 - x) = 3 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_3]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %float_1 %2\n" +
+ "%4 = OpFAdd %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 8: merge add of add
+ // (x + 1) + 2 = x + 3
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %2 %float_1\n" +
+ "%4 = OpFAdd %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 9: merge add of add
+ // (1 + x) + 2 = 3 + x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %float_1 %2\n" +
+ "%4 = OpFAdd %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 10: merge add of add
+ // 2 + (x + 1) = x + 1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %2 %float_1\n" +
+ "%4 = OpFAdd %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 11: merge add of add
+ // 2 + (1 + x) = 3 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_3]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %float_1 %2\n" +
+ "%4 = OpFAdd %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true)
+));
+
+INSTANTIATE_TEST_CASE_P(MergeSubTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: merge sub of negate
+ // (-x) - 2 = -2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n2:%\\w+]] = OpConstant [[float]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFNegate %float %2\n" +
+ "%4 = OpFSub %float %3 %float_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 1: merge sub of negate
+ // 2 - (-x) = x + 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_2:%\\w+]] = OpConstant [[float]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFNegate %float %2\n" +
+ "%4 = OpFSub %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 2: merge sub of negate
+ // (-x) - 2 = -2 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
+ "; CHECK: [[long_n2:%\\w+]] = OpConstant [[long]] -2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpISub [[long]] [[long_n2]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpSNegate %long %2\n" +
+ "%4 = OpISub %long %3 %long_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 3: merge sub of negate
+ // 2 - (-x) = x + 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[long:%\\w+]] = OpTypeInt 64 1\n" +
+ "; CHECK: [[long_2:%\\w+]] = OpConstant [[long]] 2\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[long]]\n" +
+ "; CHECK: %4 = OpIAdd [[long]] [[ld]] [[long_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_long Function\n" +
+ "%2 = OpLoad %long %var\n" +
+ "%3 = OpSNegate %long %2\n" +
+ "%4 = OpISub %long %long_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 4: merge add of subtract
+ // (x + 2) - 1 = x + 1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %2 %float_2\n" +
+ "%4 = OpFSub %float %3 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 5: merge add of subtract
+ // (2 + x) - 1 = x + 1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %float_2 %2\n" +
+ "%4 = OpFSub %float %3 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 6: merge add of subtract
+ // 2 - (x + 1) = 1 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_1]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %2 %float_1\n" +
+ "%4 = OpFSub %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 7: merge add of subtract
+ // 2 - (1 + x) = 1 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_1]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFAdd %float %float_1 %2\n" +
+ "%4 = OpFSub %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 8: merge subtract of subtract
+ // (x - 2) - 1 = x - 3
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[ld]] [[float_3]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %2 %float_2\n" +
+ "%4 = OpFSub %float %3 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 9: merge subtract of subtract
+ // (2 - x) - 1 = 1 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_1]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %float_2 %2\n" +
+ "%4 = OpFSub %float %3 %float_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 10: merge subtract of subtract
+ // 2 - (x - 1) = 3 - x
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_3:%\\w+]] = OpConstant [[float]] 3\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFSub [[float]] [[float_3]] [[ld]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %2 %float_1\n" +
+ "%4 = OpFSub %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 11: merge subtract of subtract
+ // 1 - (2 - x) = x + (-1)
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_n1:%\\w+]] = OpConstant [[float]] -1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_n1]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %float_2 %2\n" +
+ "%4 = OpFSub %float %float_1 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true),
+ // Test case 12: merge subtract of subtract
+ // 2 - (1 - x) = x + 1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: [[float_1:%\\w+]] = OpConstant [[float]] 1\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[float]]\n" +
+ "; CHECK: %4 = OpFAdd [[float]] [[ld]] [[float_1]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%var = OpVariable %_ptr_float Function\n" +
+ "%2 = OpLoad %float %var\n" +
+ "%3 = OpFSub %float %float_1 %2\n" +
+ "%4 = OpFSub %float %float_2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd\n",
+ 4, true)
+));
+
+INSTANTIATE_TEST_CASE_P(SelectFoldingTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Fold select with the same values for both sides
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" +
+ "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_bool Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%2 = OpSelect %int %load %100 %100\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 1: Fold select true to left side
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" +
+ "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%2 = OpSelect %int %true %100 %n\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 2: Fold select false to right side
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" +
+ "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %bool %n\n" +
+ "%2 = OpSelect %int %false %n %100\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 3: Fold select null to right side
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[int0:%\\w+]] = OpConstant [[int]] 0\n" +
+ "; CHECK: %2 = OpCopyObject [[int]] [[int0]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_int Function\n" +
+ "%load = OpLoad %int %n\n" +
+ "%2 = OpSelect %int %bool_null %load %100\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 4: vector null
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
+ "; CHECK: [[int2:%\\w+]] = OpConstant [[int]] 2\n" +
+ "; CHECK: [[v2int2_2:%\\w+]] = OpConstantComposite [[v2int]] [[int2]] [[int2]]\n" +
+ "; CHECK: %2 = OpCopyObject [[v2int]] [[v2int2_2]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%load = OpLoad %v2int %n\n" +
+ "%2 = OpSelect %v2int %v2bool_null %load %v2int_2_2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 2, true),
+ // Test case 5: vector select
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
+ "; CHECK: %4 = OpVectorShuffle [[v2int]] %2 %3 0 3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%m = OpVariable %_ptr_v2int Function\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%2 = OpLoad %v2int %n\n" +
+ "%3 = OpLoad %v2int %n\n" +
+ "%4 = OpSelect %v2int %v2bool_true_false %2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 6: vector select
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: [[v2int:%\\w+]] = OpTypeVector [[int]] 2\n" +
+ "; CHECK: %4 = OpVectorShuffle [[v2int]] %2 %3 2 1\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%m = OpVariable %_ptr_v2int Function\n" +
+ "%n = OpVariable %_ptr_v2int Function\n" +
+ "%2 = OpLoad %v2int %n\n" +
+ "%3 = OpLoad %v2int %n\n" +
+ "%4 = OpSelect %v2int %v2bool_false_true %2 %3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true)
+));
+
+INSTANTIATE_TEST_CASE_P(CompositeExtractMatchingTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Extracting from result of consecutive shuffles of differing
+ // size.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: %5 = OpCompositeExtract [[int]] %2 2\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4int Function\n" +
+ "%2 = OpLoad %v4int %n\n" +
+ "%3 = OpVectorShuffle %v2int %2 %2 2 3\n" +
+ "%4 = OpVectorShuffle %v4int %2 %3 0 4 2 5\n" +
+ "%5 = OpCompositeExtract %int %4 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, true),
+ // Test case 1: Extracting from result of vector shuffle of differing
+ // input and result sizes.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: %4 = OpCompositeExtract [[int]] %2 2\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4int Function\n" +
+ "%2 = OpLoad %v4int %n\n" +
+ "%3 = OpVectorShuffle %v2int %2 %2 2 3\n" +
+ "%4 = OpCompositeExtract %int %3 0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 2: Extracting from result of vector shuffle of differing
+ // input and result sizes.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: %4 = OpCompositeExtract [[int]] %2 3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4int Function\n" +
+ "%2 = OpLoad %v4int %n\n" +
+ "%3 = OpVectorShuffle %v2int %2 %2 2 3\n" +
+ "%4 = OpCompositeExtract %int %3 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true),
+ // Test case 3: Using fmix feeding extract with a 1 in the a position.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 4\n" +
+ "; CHECK: [[ptr_v4double:%\\w+]] = OpTypePointer Function [[v4double]]\n" +
+ "; CHECK: [[m:%\\w+]] = OpVariable [[ptr_v4double]] Function\n" +
+ "; CHECK: [[n:%\\w+]] = OpVariable [[ptr_v4double]] Function\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v4double]] [[n]]\n" +
+ "; CHECK: %5 = OpCompositeExtract [[double]] [[ld]] 1\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%m = OpVariable %_ptr_v4double Function\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%2 = OpLoad %v4double %m\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%4 = OpExtInst %v4double %1 FMix %2 %3 %v4double_0_1_0_0\n" +
+ "%5 = OpCompositeExtract %double %4 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, true),
+ // Test case 4: Using fmix feeding extract with a 0 in the a position.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 4\n" +
+ "; CHECK: [[ptr_v4double:%\\w+]] = OpTypePointer Function [[v4double]]\n" +
+ "; CHECK: [[m:%\\w+]] = OpVariable [[ptr_v4double]] Function\n" +
+ "; CHECK: [[n:%\\w+]] = OpVariable [[ptr_v4double]] Function\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v4double]] [[m]]\n" +
+ "; CHECK: %5 = OpCompositeExtract [[double]] [[ld]] 2\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%m = OpVariable %_ptr_v4double Function\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%2 = OpLoad %v4double %m\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%4 = OpExtInst %v4double %1 FMix %2 %3 %v4double_0_1_0_0\n" +
+ "%5 = OpCompositeExtract %double %4 2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, true),
+ // Test case 5: Using fmix feeding extract with a null for the alpha
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 4\n" +
+ "; CHECK: [[ptr_v4double:%\\w+]] = OpTypePointer Function [[v4double]]\n" +
+ "; CHECK: [[m:%\\w+]] = OpVariable [[ptr_v4double]] Function\n" +
+ "; CHECK: [[n:%\\w+]] = OpVariable [[ptr_v4double]] Function\n" +
+ "; CHECK: [[ld:%\\w+]] = OpLoad [[v4double]] [[m]]\n" +
+ "; CHECK: %5 = OpCompositeExtract [[double]] [[ld]] 0\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%m = OpVariable %_ptr_v4double Function\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%2 = OpLoad %v4double %m\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%4 = OpExtInst %v4double %1 FMix %2 %3 %v4double_null\n" +
+ "%5 = OpCompositeExtract %double %4 0\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, true),
+ // Test case 6: Don't fold: Using fmix feeding extract with 0.5 in the a
+ // position.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%m = OpVariable %_ptr_v4double Function\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%2 = OpLoad %v4double %m\n" +
+ "%3 = OpLoad %v4double %n\n" +
+ "%4 = OpExtInst %v4double %1 FMix %2 %3 %v4double_1_1_1_0p5\n" +
+ "%5 = OpCompositeExtract %double %4 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 5, false),
+ // Test case 7: Extracting the undefined literal value from a vector
+ // shuffle.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[int:%\\w+]] = OpTypeInt 32 1\n" +
+ "; CHECK: %4 = OpUndef [[int]]\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4int Function\n" +
+ "%2 = OpLoad %v4int %n\n" +
+ "%3 = OpVectorShuffle %v2int %2 %2 2 4294967295\n" +
+ "%4 = OpCompositeExtract %int %3 1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 4, true)
+));
+
+INSTANTIATE_TEST_CASE_P(DotProductMatchingTest, MatchingInstructionFoldingTest,
+::testing::Values(
+ // Test case 0: Using OpDot to extract last element.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: %3 = OpCompositeExtract [[float]] %2 3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%2 = OpLoad %v4float %n\n" +
+ "%3 = OpDot %float %2 %v4float_0_0_0_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, true),
+ // Test case 1: Using OpDot to extract last element.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: %3 = OpCompositeExtract [[float]] %2 3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%2 = OpLoad %v4float %n\n" +
+ "%3 = OpDot %float %v4float_0_0_0_1 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, true),
+ // Test case 2: Using OpDot to extract second element.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[float:%\\w+]] = OpTypeFloat 32\n" +
+ "; CHECK: %3 = OpCompositeExtract [[float]] %2 1\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4float Function\n" +
+ "%2 = OpLoad %v4float %n\n" +
+ "%3 = OpDot %float %v4float_0_1_0_0 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, true),
+ // Test case 3: Using OpDot to extract last element.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: %3 = OpCompositeExtract [[double]] %2 3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%2 = OpLoad %v4double %n\n" +
+ "%3 = OpDot %double %2 %v4double_0_0_0_1\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, true),
+ // Test case 4: Using OpDot to extract last element.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: %3 = OpCompositeExtract [[double]] %2 3\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%2 = OpLoad %v4double %n\n" +
+ "%3 = OpDot %double %v4double_0_0_0_1 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, true),
+ // Test case 5: Using OpDot to extract second element.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: %3 = OpCompositeExtract [[double]] %2 1\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%2 = OpLoad %v4double %n\n" +
+ "%3 = OpDot %double %v4double_0_1_0_0 %2\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 3, true)
+));
+
+using MatchingInstructionWithNoResultFoldingTest =
+::testing::TestWithParam<InstructionFoldingCase<bool>>;
+
+// Test folding instructions that do not have a result. The instruction
+// that will be folded is the last instruction before the return. If there
+// are multiple returns, there is not guarentee which one is used.
+TEST_P(MatchingInstructionWithNoResultFoldingTest, Case) {
+ const auto& tc = GetParam();
+
+ // Build module.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, tc.test_body,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+
+ // Fold the instruction to test.
+ Instruction* inst = nullptr;
+ Function* func = &*context->module()->begin();
+ for (auto& bb : *func) {
+ Instruction* terminator = bb.terminator();
+ if (terminator->IsReturnOrAbort()) {
+ inst = terminator->PreviousNode();
+ break;
+ }
+ }
+ assert(inst && "Invalid test. Could not find instruction to fold.");
+ std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
+ bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
+ EXPECT_EQ(succeeded, tc.expected_result);
+ if (succeeded) {
+ Match(tc.test_body, context.get());
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(StoreMatchingTest, MatchingInstructionWithNoResultFoldingTest,
+::testing::Values(
+ // Test case 0: Remove store of undef.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpLabel\n" +
+ "; CHECK-NOT: OpStore\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%undef = OpUndef %v4double\n" +
+ "OpStore %n %undef\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 0 /* OpStore */, true),
+ // Test case 1: Keep volatile store.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%n = OpVariable %_ptr_v4double Function\n" +
+ "%undef = OpUndef %v4double\n" +
+ "OpStore %n %undef Volatile\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 0 /* OpStore */, false)
+));
+
+INSTANTIATE_TEST_CASE_P(VectorShuffleMatchingTest, MatchingInstructionWithNoResultFoldingTest,
+::testing::Values(
+ // Test case 0: Basic test 1
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %7 %5 2 3 6 7\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %6 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %7 %8 2 3 4 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 1: Basic test 2
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %6 %7 0 1 4 5\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %6 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %8 %7 2 3 4 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 2: Basic test 3
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %5 %7 3 2 4 5\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %6 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %8 %7 1 0 4 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 3: Basic test 4
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %7 %6 2 3 5 4\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %6 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %7 %8 2 3 7 6\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 4: Don't fold, need both operands of the feeder.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %6 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %7 %8 2 3 7 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, false),
+ // Test case 5: Don't fold, need both operands of the feeder.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %6 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %8 %7 2 0 7 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, false),
+ // Test case 6: Fold, need both operands of the feeder, but they are the same.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %5 %7 0 2 7 5\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %5 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %8 %7 2 0 7 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 7: Fold, need both operands of the feeder, but they are the same.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %7 %5 2 0 5 7\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %5 2 3 4 5\n" +
+ "%9 = OpVectorShuffle %v4double %7 %8 2 0 7 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 8: Replace first operand with a smaller vector.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %5 %7 0 0 5 3\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v2double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v2double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v4double %5 %5 0 1 2 3\n" +
+ "%9 = OpVectorShuffle %v4double %8 %7 2 0 7 5\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 9: Replace first operand with a larger vector.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %5 %7 3 0 7 5\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v2double %5 %5 0 3\n" +
+ "%9 = OpVectorShuffle %v4double %8 %7 1 0 5 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 10: Replace unused operand with null.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: [[null:%\\w+]] = OpConstantNull [[v4double]]\n" +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} [[null]] %7 4 2 5 3\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v2double %5 %5 0 3\n" +
+ "%9 = OpVectorShuffle %v4double %8 %7 4 2 5 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 11: Replace unused operand with null.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: [[null:%\\w+]] = OpConstantNull [[v4double]]\n" +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} [[null]] %5 2 2 5 5\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%8 = OpVectorShuffle %v2double %5 %5 0 3\n" +
+ "%9 = OpVectorShuffle %v4double %8 %8 2 2 3 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 12: Replace unused operand with null.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: [[null:%\\w+]] = OpConstantNull [[v4double]]\n" +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %7 [[null]] 2 0 1 3\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v2double %5 %5 0 3\n" +
+ "%9 = OpVectorShuffle %v4double %7 %8 2 0 1 3\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true),
+ // Test case 13: Shuffle with undef literal.
+ InstructionFoldingCase<bool>(
+ Header() +
+ "; CHECK: [[double:%\\w+]] = OpTypeFloat 64\n" +
+ "; CHECK: [[v4double:%\\w+]] = OpTypeVector [[double]] 2\n" +
+ "; CHECK: OpVectorShuffle\n" +
+ "; CHECK: OpVectorShuffle {{%\\w+}} %7 {{%\\w+}} 2 0 1 4294967295\n" +
+ "; CHECK: OpReturn\n" +
+ "%main = OpFunction %void None %void_func\n" +
+ "%main_lab = OpLabel\n" +
+ "%2 = OpVariable %_ptr_v4double Function\n" +
+ "%3 = OpVariable %_ptr_v4double Function\n" +
+ "%4 = OpVariable %_ptr_v4double Function\n" +
+ "%5 = OpLoad %v4double %2\n" +
+ "%6 = OpLoad %v4double %3\n" +
+ "%7 = OpLoad %v4double %4\n" +
+ "%8 = OpVectorShuffle %v2double %5 %5 0 1\n" +
+ "%9 = OpVectorShuffle %v4double %7 %8 2 0 1 4294967295\n" +
+ "OpReturn\n" +
+ "OpFunctionEnd",
+ 9, true)
+));
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/freeze_spec_const_test.cpp b/test/opt/freeze_spec_const_test.cpp
new file mode 100644
index 0000000..5cc7843
--- /dev/null
+++ b/test/opt/freeze_spec_const_test.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) 2016 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 <algorithm>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+struct FreezeSpecConstantValueTypeTestCase {
+ const char* type_decl;
+ const char* spec_const;
+ const char* expected_frozen_const;
+};
+
+using FreezeSpecConstantValueTypeTest =
+ PassTest<::testing::TestWithParam<FreezeSpecConstantValueTypeTestCase>>;
+
+TEST_P(FreezeSpecConstantValueTypeTest, PrimaryType) {
+ auto& test_case = GetParam();
+ std::vector<const char*> text = {"OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ test_case.type_decl, test_case.spec_const};
+ std::vector<const char*> expected = {
+ "OpCapability Shader", "OpMemoryModel Logical GLSL450",
+ test_case.type_decl, test_case.expected_frozen_const};
+ SinglePassRunAndCheck<FreezeSpecConstantValuePass>(
+ JoinAllInsts(text), JoinAllInsts(expected), /* skip_nop = */ false);
+}
+
+// Test each primary type.
+INSTANTIATE_TEST_CASE_P(
+ PrimaryTypeSpecConst, FreezeSpecConstantValueTypeTest,
+ ::testing::ValuesIn(std::vector<FreezeSpecConstantValueTypeTestCase>({
+ // Type declaration, original spec constant definition, expected frozen
+ // spec constants.
+ {"%int = OpTypeInt 32 1", "%2 = OpSpecConstant %int 1",
+ "%int_1 = OpConstant %int 1"},
+ {"%uint = OpTypeInt 32 0", "%2 = OpSpecConstant %uint 1",
+ "%uint_1 = OpConstant %uint 1"},
+ {"%float = OpTypeFloat 32", "%2 = OpSpecConstant %float 3.1415",
+ "%float_3_1415 = OpConstant %float 3.1415"},
+ {"%double = OpTypeFloat 64", "%2 = OpSpecConstant %double 3.141592653",
+ "%double_3_141592653 = OpConstant %double 3.141592653"},
+ {"%bool = OpTypeBool", "%2 = OpSpecConstantTrue %bool",
+ "%true = OpConstantTrue %bool"},
+ {"%bool = OpTypeBool", "%2 = OpSpecConstantFalse %bool",
+ "%false = OpConstantFalse %bool"},
+ })));
+
+using FreezeSpecConstantValueRemoveDecorationTest = PassTest<::testing::Test>;
+
+TEST_F(FreezeSpecConstantValueRemoveDecorationTest,
+ RemoveDecorationInstWithSpecId) {
+ std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "OpCapability Float64",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpSource GLSL 450",
+ "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"",
+ "OpSourceExtension \"GL_GOOGLE_include_directive\"",
+ "OpName %main \"main\"",
+ "OpDecorate %3 SpecId 200",
+ "OpDecorate %4 SpecId 201",
+ "OpDecorate %5 SpecId 202",
+ "OpDecorate %6 SpecId 203",
+ "%void = OpTypeVoid",
+ "%8 = OpTypeFunction %void",
+ "%int = OpTypeInt 32 1",
+ "%3 = OpSpecConstant %int 3",
+ "%float = OpTypeFloat 32",
+ "%4 = OpSpecConstant %float 3.1415",
+ "%double = OpTypeFloat 64",
+ "%5 = OpSpecConstant %double 3.14159265358979",
+ "%bool = OpTypeBool",
+ "%6 = OpSpecConstantTrue %bool",
+ "%13 = OpSpecConstantFalse %bool",
+ "%main = OpFunction %void None %8",
+ "%14 = OpLabel",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ std::string expected_disassembly = SelectiveJoin(text, [](const char* line) {
+ return std::string(line).find("SpecId") != std::string::npos;
+ });
+ std::vector<std::pair<const char*, const char*>> replacement_pairs = {
+ {"%3 = OpSpecConstant %int 3", "%int_3 = OpConstant %int 3"},
+ {"%4 = OpSpecConstant %float 3.1415",
+ "%float_3_1415 = OpConstant %float 3.1415"},
+ {"%5 = OpSpecConstant %double 3.14159265358979",
+ "%double_3_14159265358979 = OpConstant %double 3.14159265358979"},
+ {"%6 = OpSpecConstantTrue ", "%true = OpConstantTrue "},
+ {"%13 = OpSpecConstantFalse ", "%false = OpConstantFalse "},
+ };
+ for (auto& p : replacement_pairs) {
+ EXPECT_TRUE(FindAndReplace(&expected_disassembly, p.first, p.second))
+ << "text:\n"
+ << expected_disassembly << "\n"
+ << "find_str:\n"
+ << p.first << "\n"
+ << "replace_str:\n"
+ << p.second << "\n";
+ }
+ SinglePassRunAndCheck<FreezeSpecConstantValuePass>(JoinAllInsts(text),
+ expected_disassembly,
+ /* skip_nop = */ true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/function_test.cpp b/test/opt/function_test.cpp
new file mode 100644
index 0000000..38ab298
--- /dev/null
+++ b/test/opt/function_test.cpp
@@ -0,0 +1,173 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "function_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::Eq;
+
+TEST(FunctionTest, IsNotRecursive) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+OpDecorate %2 DescriptorSet 439418829
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %12
+OpUnreachable
+OpFunctionEnd
+%12 = OpFunction %_struct_6 None %7
+%13 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> ctx =
+ spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ auto* func = spvtest::GetFunction(ctx->module(), 9);
+ EXPECT_FALSE(func->IsRecursive());
+
+ func = spvtest::GetFunction(ctx->module(), 12);
+ EXPECT_FALSE(func->IsRecursive());
+}
+
+TEST(FunctionTest, IsDirectlyRecursive) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+OpDecorate %2 DescriptorSet 439418829
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> ctx =
+ spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ auto* func = spvtest::GetFunction(ctx->module(), 9);
+ EXPECT_TRUE(func->IsRecursive());
+}
+
+TEST(FunctionTest, IsIndirectlyRecursive) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+OpDecorate %2 DescriptorSet 439418829
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %12
+OpUnreachable
+OpFunctionEnd
+%12 = OpFunction %_struct_6 None %7
+%13 = OpLabel
+%14 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> ctx =
+ spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ auto* func = spvtest::GetFunction(ctx->module(), 9);
+ EXPECT_TRUE(func->IsRecursive());
+
+ func = spvtest::GetFunction(ctx->module(), 12);
+ EXPECT_TRUE(func->IsRecursive());
+}
+
+TEST(FunctionTest, IsNotRecuriseCallingRecursive) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+OpDecorate %2 DescriptorSet 439418829
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> ctx =
+ spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ auto* func = spvtest::GetFunction(ctx->module(), 1);
+ EXPECT_FALSE(func->IsRecursive());
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/function_utils.h b/test/opt/function_utils.h
new file mode 100644
index 0000000..803cacd
--- /dev/null
+++ b/test/opt/function_utils.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef TEST_OPT_FUNCTION_UTILS_H_
+#define TEST_OPT_FUNCTION_UTILS_H_
+
+#include "source/opt/function.h"
+#include "source/opt/module.h"
+
+namespace spvtest {
+
+inline spvtools::opt::Function* GetFunction(spvtools::opt::Module* module,
+ uint32_t id) {
+ for (spvtools::opt::Function& f : *module) {
+ if (f.result_id() == id) {
+ return &f;
+ }
+ }
+ return nullptr;
+}
+
+inline const spvtools::opt::Function* GetFunction(
+ const spvtools::opt::Module* module, uint32_t id) {
+ for (const spvtools::opt::Function& f : *module) {
+ if (f.result_id() == id) {
+ return &f;
+ }
+ }
+ return nullptr;
+}
+
+inline const spvtools::opt::BasicBlock* GetBasicBlock(
+ const spvtools::opt::Function* fn, uint32_t id) {
+ for (const spvtools::opt::BasicBlock& bb : *fn) {
+ if (bb.id() == id) {
+ return &bb;
+ }
+ }
+ return nullptr;
+}
+
+} // namespace spvtest
+
+#endif // TEST_OPT_FUNCTION_UTILS_H_
diff --git a/test/opt/if_conversion_test.cpp b/test/opt/if_conversion_test.cpp
new file mode 100644
index 0000000..03932a9
--- /dev/null
+++ b/test/opt/if_conversion_test.cpp
@@ -0,0 +1,511 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using IfConversionTest = PassTest<::testing::Test>;
+
+TEST_F(IfConversionTest, TestSimpleIfThenElse) {
+ const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %16
+%15 = OpLabel
+OpBranch %14
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %15 %uint_1 %16
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestSimpleHalfIfTrue) {
+ const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %14
+%15 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %15 %uint_1 %12
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestSimpleHalfIfExtraBlock) {
+ const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %14
+%15 = OpLabel
+OpBranch %16
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %15 %uint_1 %12
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestSimpleHalfIfFalse) {
+ const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %14 %15
+%15 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %12 %uint_1 %15
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestVectorSplat) {
+ const std::string text = R"(
+; CHECK: [[bool_vec:%\w+]] = OpTypeVector %bool 2
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[comp:%\w+]] = OpCompositeConstruct [[bool_vec]] %true %true
+; CHECK: [[sel:%\w+]] = OpSelect {{%\w+}} [[comp]]
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_vec2 = OpTypeVector %uint 2
+%vec2_01 = OpConstantComposite %uint_vec2 %uint_0 %uint_1
+%vec2_10 = OpConstantComposite %uint_vec2 %uint_1 %uint_0
+%_ptr_Output_uint = OpTypePointer Output %uint_vec2
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %16
+%15 = OpLabel
+OpBranch %14
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint_vec2 %vec2_01 %15 %vec2_10 %16
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, CodeMotionSameValue) {
+ const std::string text = R"(
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK-NOT: OpLabel
+; CHECK: [[add:%\w+]] = OpIAdd %uint %uint_0 %uint_1
+; CHECK: OpSelectionMerge [[merge_lab:%\w+]] None
+; CHECK-NEXT: OpBranchConditional
+; CHECK: [[merge_lab]] = OpLabel
+; CHECK-NOT: OpLabel
+; CHECK: OpStore [[var]] [[add]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "func" %2
+ %void = OpTypeVoid
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+ %2 = OpVariable %_ptr_Output_uint Output
+ %8 = OpTypeFunction %void
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %1 = OpFunction %void None %8
+ %11 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %true %13 %15
+ %13 = OpLabel
+ %14 = OpIAdd %uint %uint_0 %uint_1
+ OpBranch %12
+ %15 = OpLabel
+ %16 = OpIAdd %uint %uint_0 %uint_1
+ OpBranch %12
+ %12 = OpLabel
+ %17 = OpPhi %uint %16 %15 %14 %13
+ OpStore %2 %17
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, CodeMotionMultipleInstructions) {
+ const std::string text = R"(
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK-NOT: OpLabel
+; CHECK: [[a1:%\w+]] = OpIAdd %uint %uint_0 %uint_1
+; CHECK: [[a2:%\w+]] = OpIAdd %uint [[a1]] %uint_1
+; CHECK: OpSelectionMerge [[merge_lab:%\w+]] None
+; CHECK-NEXT: OpBranchConditional
+; CHECK: [[merge_lab]] = OpLabel
+; CHECK-NOT: OpLabel
+; CHECK: OpStore [[var]] [[a2]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %1 "func" %2
+ %void = OpTypeVoid
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+ %2 = OpVariable %_ptr_Output_uint Output
+ %8 = OpTypeFunction %void
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %1 = OpFunction %void None %8
+ %11 = OpLabel
+ OpSelectionMerge %12 None
+ OpBranchConditional %true %13 %15
+ %13 = OpLabel
+ %a1 = OpIAdd %uint %uint_0 %uint_1
+ %a2 = OpIAdd %uint %a1 %uint_1
+ OpBranch %12
+ %15 = OpLabel
+ %b1 = OpIAdd %uint %uint_0 %uint_1
+ %b2 = OpIAdd %uint %b1 %uint_1
+ OpBranch %12
+ %12 = OpLabel
+ %17 = OpPhi %uint %b2 %15 %a2 %13
+ OpStore %2 %17
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, NoCommonDominator) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%1 = OpFunction %void None %8
+%9 = OpLabel
+OpBranch %10
+%11 = OpLabel
+OpBranch %10
+%10 = OpLabel
+%12 = OpPhi %uint %uint_0 %9 %uint_1 %11
+OpStore %2 %12
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, LoopUntouched) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpFunction %void None %8
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %uint %uint_0 %11 %uint_1 %12
+OpLoopMerge %14 %12 None
+OpBranchConditional %true %14 %12
+%14 = OpLabel
+OpStore %2 %13
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, TooManyPredecessors) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpFunction %void None %8
+%11 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %true %13 %12
+%13 = OpLabel
+OpBranchConditional %true %14 %15
+%14 = OpLabel
+OpBranch %12
+%15 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%16 = OpPhi %uint %uint_0 %11 %uint_0 %14 %uint_1 %15
+OpStore %2 %16
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, NoCodeMotion) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpFunction %void None %8
+%11 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %true %13 %12
+%13 = OpLabel
+%14 = OpIAdd %uint %uint_0 %uint_1
+OpBranch %12
+%12 = OpLabel
+%15 = OpPhi %uint %uint_0 %11 %14 %13
+OpStore %2 %15
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, NoCodeMotionImmovableInst) {
+ const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpFunction %void None %8
+%11 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %true %13 %14
+%13 = OpLabel
+OpSelectionMerge %15 None
+OpBranchConditional %true %16 %15
+%16 = OpLabel
+%17 = OpIAdd %uint %uint_0 %uint_1
+OpBranch %15
+%15 = OpLabel
+%18 = OpPhi %uint %uint_0 %13 %17 %16
+%19 = OpIAdd %uint %18 %uint_1
+OpBranch %12
+%14 = OpLabel
+OpSelectionMerge %20 None
+OpBranchConditional %true %21 %20
+%21 = OpLabel
+%22 = OpIAdd %uint %uint_0 %uint_1
+OpBranch %20
+%20 = OpLabel
+%23 = OpPhi %uint %uint_0 %14 %22 %21
+%24 = OpIAdd %uint %23 %uint_1
+OpBranch %12
+%12 = OpLabel
+%25 = OpPhi %uint %24 %20 %19 %15
+OpStore %2 %25
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, InvalidCommonDominator) {
+ const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpTypeFunction %void
+%2 = OpFunction %void None %1
+%3 = OpLabel
+OpBranch %4
+%4 = OpLabel
+OpLoopMerge %5 %6 None
+OpBranch %7
+%7 = OpLabel
+OpSelectionMerge %8 None
+OpBranchConditional %true %8 %9
+%9 = OpLabel
+OpSelectionMerge %10 None
+OpBranchConditional %true %10 %5
+%10 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpBranch %6
+%6 = OpLabel
+OpBranchConditional %true %4 %5
+%5 = OpLabel
+%11 = OpPhi %float %float_0 %6 %float_1 %9
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<IfConversion>(text, text, true, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/inline_opaque_test.cpp b/test/opt/inline_opaque_test.cpp
new file mode 100644
index 0000000..d10913a
--- /dev/null
+++ b/test/opt/inline_opaque_test.cpp
@@ -0,0 +1,412 @@
+// 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 InlineOpaqueTest = PassTest<::testing::Test>;
+
+TEST_F(InlineOpaqueTest, InlineCallWithStructArgContainingSampledImage) {
+ // Function with opaque argument is inlined.
+ // TODO(greg-lunarg): Add HLSL code
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %outColor %texCoords
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpMemberName %S_t 2 "smp"
+OpName %foo_struct_S_t_vf2_vf21_ "foo(struct-S_t-vf2-vf21;"
+OpName %s "s"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %s0 "s0"
+OpName %texCoords "texCoords"
+OpName %param "param"
+OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%18 = OpTypeSampledImage %17
+%S_t = OpTypeStruct %v2float %v2float %18
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%20 = OpTypeFunction %void %_ptr_Function_S_t
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Function_18 = OpTypePointer Function %18
+%sampler15 = OpVariable %_ptr_UniformConstant_18 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %12
+%28 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%29 = OpLoad %v2float %texCoords
+%30 = OpAccessChain %_ptr_Function_v2float %s0 %int_0
+OpStore %30 %29
+%31 = OpLoad %18 %sampler15
+%32 = OpAccessChain %_ptr_Function_18 %s0 %int_2
+OpStore %32 %31
+%33 = OpLoad %S_t %s0
+OpStore %param %33
+%34 = OpFunctionCall %void %foo_struct_S_t_vf2_vf21_ %param
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %12
+%28 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%29 = OpLoad %v2float %texCoords
+%30 = OpAccessChain %_ptr_Function_v2float %s0 %int_0
+OpStore %30 %29
+%31 = OpLoad %18 %sampler15
+%32 = OpAccessChain %_ptr_Function_18 %s0 %int_2
+OpStore %32 %31
+%33 = OpLoad %S_t %s0
+OpStore %param %33
+%41 = OpAccessChain %_ptr_Function_18 %param %int_2
+%42 = OpLoad %18 %41
+%43 = OpAccessChain %_ptr_Function_v2float %param %int_0
+%44 = OpLoad %v2float %43
+%45 = OpImageSampleImplicitLod %v4float %42 %44
+OpStore %outColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string post_defs =
+ R"(%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %20
+%s = OpFunctionParameter %_ptr_Function_S_t
+%35 = OpLabel
+%36 = OpAccessChain %_ptr_Function_18 %s %int_2
+%37 = OpLoad %18 %36
+%38 = OpAccessChain %_ptr_Function_v2float %s %int_0
+%39 = OpLoad %v2float %38
+%40 = OpImageSampleImplicitLod %v4float %37 %39
+OpStore %outColor %40
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineOpaquePass>(
+ predefs + before + post_defs, predefs + after + post_defs, true, true);
+}
+
+TEST_F(InlineOpaqueTest, InlineOpaqueReturn) {
+ // Function with opaque return value is inlined.
+ // TODO(greg-lunarg): Add HLSL code
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %texCoords %outColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_ "foo("
+OpName %texCoords "texCoords"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %sampler16 "sampler16"
+OpDecorate %sampler15 DescriptorSet 0
+OpDecorate %sampler16 DescriptorSet 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+%float_0 = OpConstant %float 0
+%16 = OpConstantComposite %v2float %float_0 %float_0
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%19 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%20 = OpTypeSampledImage %19
+%21 = OpTypeFunction %20
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%_ptr_Function_20 = OpTypePointer Function %20
+%sampler15 = OpVariable %_ptr_UniformConstant_20 UniformConstant
+%sampler16 = OpVariable %_ptr_UniformConstant_20 UniformConstant
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%24 = OpLabel
+%25 = OpVariable %_ptr_Function_20 Function
+%26 = OpFunctionCall %20 %foo_
+OpStore %25 %26
+%27 = OpLoad %20 %25
+%28 = OpLoad %v2float %texCoords
+%29 = OpImageSampleImplicitLod %v4float %27 %28
+OpStore %outColor %29
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%24 = OpLabel
+%34 = OpVariable %_ptr_Function_20 Function
+%35 = OpVariable %_ptr_Function_20 Function
+%25 = OpVariable %_ptr_Function_20 Function
+%36 = OpLoad %20 %sampler16
+OpStore %34 %36
+%37 = OpLoad %20 %34
+OpStore %35 %37
+%26 = OpLoad %20 %35
+OpStore %25 %26
+%27 = OpLoad %20 %25
+%28 = OpLoad %v2float %texCoords
+%29 = OpImageSampleImplicitLod %v4float %27 %28
+OpStore %outColor %29
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string post_defs =
+ R"(%foo_ = OpFunction %20 None %21
+%30 = OpLabel
+%31 = OpVariable %_ptr_Function_20 Function
+%32 = OpLoad %20 %sampler16
+OpStore %31 %32
+%33 = OpLoad %20 %31
+OpReturnValue %33
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineOpaquePass>(
+ predefs + before + post_defs, predefs + after + post_defs, true, true);
+}
+
+TEST_F(InlineOpaqueTest, InlineInNonEntryPointFunction) {
+ // This demonstrates opaque inlining in a function that is not
+ // an entry point function (main2) but is in the call tree of an
+ // entry point function (main).
+ // TODO(greg-lunarg): Add HLSL code
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %outColor %texCoords
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %main2 "main2"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpMemberName %S_t 2 "smp"
+OpName %foo_struct_S_t_vf2_vf21_ "foo(struct-S_t-vf2-vf21;"
+OpName %s "s"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %s0 "s0"
+OpName %texCoords "texCoords"
+OpName %param "param"
+OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%19 = OpTypeSampledImage %18
+%S_t = OpTypeStruct %v2float %v2float %19
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%21 = OpTypeFunction %void %_ptr_Function_S_t
+%_ptr_UniformConstant_19 = OpTypePointer UniformConstant %19
+%_ptr_Function_19 = OpTypePointer Function %19
+%sampler15 = OpVariable %_ptr_UniformConstant_19 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string before =
+ R"(%main2 = OpFunction %void None %13
+%29 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%30 = OpLoad %v2float %texCoords
+%31 = OpAccessChain %_ptr_Function_v2float %s0 %int_0
+OpStore %31 %30
+%32 = OpLoad %19 %sampler15
+%33 = OpAccessChain %_ptr_Function_19 %s0 %int_2
+OpStore %33 %32
+%34 = OpLoad %S_t %s0
+OpStore %param %34
+%35 = OpFunctionCall %void %foo_struct_S_t_vf2_vf21_ %param
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main2 = OpFunction %void None %13
+%29 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%30 = OpLoad %v2float %texCoords
+%31 = OpAccessChain %_ptr_Function_v2float %s0 %int_0
+OpStore %31 %30
+%32 = OpLoad %19 %sampler15
+%33 = OpAccessChain %_ptr_Function_19 %s0 %int_2
+OpStore %33 %32
+%34 = OpLoad %S_t %s0
+OpStore %param %34
+%44 = OpAccessChain %_ptr_Function_19 %param %int_2
+%45 = OpLoad %19 %44
+%46 = OpAccessChain %_ptr_Function_v2float %param %int_0
+%47 = OpLoad %v2float %46
+%48 = OpImageSampleImplicitLod %v4float %45 %47
+OpStore %outColor %48
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string post_defs =
+ R"(%main = OpFunction %void None %13
+%36 = OpLabel
+%37 = OpFunctionCall %void %main2
+OpReturn
+OpFunctionEnd
+%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %21
+%s = OpFunctionParameter %_ptr_Function_S_t
+%38 = OpLabel
+%39 = OpAccessChain %_ptr_Function_19 %s %int_2
+%40 = OpLoad %19 %39
+%41 = OpAccessChain %_ptr_Function_v2float %s %int_0
+%42 = OpLoad %v2float %41
+%43 = OpImageSampleImplicitLod %v4float %40 %42
+OpStore %outColor %43
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineOpaquePass>(
+ predefs + before + post_defs, predefs + after + post_defs, true, true);
+}
+
+TEST_F(InlineOpaqueTest, NoInlineNoOpaque) {
+ // Function without opaque interface is not inlined.
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // return bar.x + bar.y;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+
+ const std::string assembly =
+ 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 %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%21 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %BaseColor
+OpStore %param %22
+%23 = OpFunctionCall %float %foo_vf4_ %param
+%24 = OpCompositeConstruct %v4float %23 %23 %23 %23
+OpStore %color %24
+%25 = OpLoad %v4float %color
+OpStore %gl_FragColor %25
+OpReturn
+OpFunctionEnd
+%foo_vf4_ = OpFunction %float None %14
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+%27 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%28 = OpLoad %float %27
+%29 = OpAccessChain %_ptr_Function_float %bar %uint_1
+%30 = OpLoad %float %29
+%31 = OpFAdd %float %28 %30
+OpReturnValue %31
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineOpaquePass>(assembly, assembly, true, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
new file mode 100644
index 0000000..44a9698
--- /dev/null
+++ b/test/opt/inline_test.cpp
@@ -0,0 +1,3133 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using InlineTest = PassTest<::testing::Test>;
+
+TEST_F(InlineTest, Simple) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // return bar.x + bar.y;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "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 %foo_vf4_ \"foo(vf4;\"",
+ "OpName %bar \"bar\"",
+ "OpName %color \"color\"",
+ "OpName %BaseColor \"BaseColor\"",
+ "OpName %param \"param\"",
+ "OpName %gl_FragColor \"gl_FragColor\"",
+ "%void = OpTypeVoid",
+ "%10 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+ "%14 = OpTypeFunction %float %_ptr_Function_v4float",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%uint_1 = OpConstant %uint 1",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%BaseColor = OpVariable %_ptr_Input_v4float Input",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+ "%foo_vf4_ = OpFunction %float None %14",
+ "%bar = OpFunctionParameter %_ptr_Function_v4float",
+ "%26 = OpLabel",
+ "%27 = OpAccessChain %_ptr_Function_float %bar %uint_0",
+ "%28 = OpLoad %float %27",
+ "%29 = OpAccessChain %_ptr_Function_float %bar %uint_1",
+ "%30 = OpLoad %float %29",
+ "%31 = OpFAdd %float %28 %30",
+ "OpReturnValue %31",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %10",
+ "%21 = OpLabel",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%22 = OpLoad %v4float %BaseColor",
+ "OpStore %param %22",
+ "%23 = OpFunctionCall %float %foo_vf4_ %param",
+ "%24 = OpCompositeConstruct %v4float %23 %23 %23 %23",
+ "OpStore %color %24",
+ "%25 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %25",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %10",
+ "%21 = OpLabel",
+ "%32 = OpVariable %_ptr_Function_float Function",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%22 = OpLoad %v4float %BaseColor",
+ "OpStore %param %22",
+ "%33 = OpAccessChain %_ptr_Function_float %param %uint_0",
+ "%34 = OpLoad %float %33",
+ "%35 = OpAccessChain %_ptr_Function_float %param %uint_1",
+ "%36 = OpLoad %float %35",
+ "%37 = OpFAdd %float %34 %36",
+ "OpStore %32 %37",
+ "%23 = OpLoad %float %32",
+ "%24 = OpCompositeConstruct %v4float %23 %23 %23 %23",
+ "OpStore %color %24",
+ "%25 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %25",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, Nested) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo2(float f, float f2)
+ // {
+ // return f * f2;
+ // }
+ //
+ // float foo(vec4 bar)
+ // {
+ // return foo2(bar.x + bar.y, bar.z);
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "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 %foo2_f1_f1_ \"foo2(f1;f1;\"",
+ "OpName %f \"f\"",
+ "OpName %f2 \"f2\"",
+ "OpName %foo_vf4_ \"foo(vf4;\"",
+ "OpName %bar \"bar\"",
+ "OpName %param \"param\"",
+ "OpName %param_0 \"param\"",
+ "OpName %color \"color\"",
+ "OpName %BaseColor \"BaseColor\"",
+ "OpName %param_1 \"param\"",
+ "OpName %gl_FragColor \"gl_FragColor\"",
+ "%void = OpTypeVoid",
+ "%15 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%18 = OpTypeFunction %float %_ptr_Function_float %_ptr_Function_float",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+ "%21 = OpTypeFunction %float %_ptr_Function_v4float",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+ "%uint_1 = OpConstant %uint 1",
+ "%uint_2 = OpConstant %uint 2",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%BaseColor = OpVariable %_ptr_Input_v4float Input",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+"%foo2_f1_f1_ = OpFunction %float None %18",
+ "%f = OpFunctionParameter %_ptr_Function_float",
+ "%f2 = OpFunctionParameter %_ptr_Function_float",
+ "%33 = OpLabel",
+ "%34 = OpLoad %float %f",
+ "%35 = OpLoad %float %f2",
+ "%36 = OpFMul %float %34 %35",
+ "OpReturnValue %36",
+ "OpFunctionEnd",
+ "%foo_vf4_ = OpFunction %float None %21",
+ "%bar = OpFunctionParameter %_ptr_Function_v4float",
+ "%37 = OpLabel",
+ "%param = OpVariable %_ptr_Function_float Function",
+ "%param_0 = OpVariable %_ptr_Function_float Function",
+ "%38 = OpAccessChain %_ptr_Function_float %bar %uint_0",
+ "%39 = OpLoad %float %38",
+ "%40 = OpAccessChain %_ptr_Function_float %bar %uint_1",
+ "%41 = OpLoad %float %40",
+ "%42 = OpFAdd %float %39 %41",
+ "OpStore %param %42",
+ "%43 = OpAccessChain %_ptr_Function_float %bar %uint_2",
+ "%44 = OpLoad %float %43",
+ "OpStore %param_0 %44",
+ "%45 = OpFunctionCall %float %foo2_f1_f1_ %param %param_0",
+ "OpReturnValue %45",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %15",
+ "%28 = OpLabel",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param_1 = OpVariable %_ptr_Function_v4float Function",
+ "%29 = OpLoad %v4float %BaseColor",
+ "OpStore %param_1 %29",
+ "%30 = OpFunctionCall %float %foo_vf4_ %param_1",
+ "%31 = OpCompositeConstruct %v4float %30 %30 %30 %30",
+ "OpStore %color %31",
+ "%32 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %32",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %15",
+ "%28 = OpLabel",
+ "%57 = OpVariable %_ptr_Function_float Function",
+ "%46 = OpVariable %_ptr_Function_float Function",
+ "%47 = OpVariable %_ptr_Function_float Function",
+ "%48 = OpVariable %_ptr_Function_float Function",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param_1 = OpVariable %_ptr_Function_v4float Function",
+ "%29 = OpLoad %v4float %BaseColor",
+ "OpStore %param_1 %29",
+ "%49 = OpAccessChain %_ptr_Function_float %param_1 %uint_0",
+ "%50 = OpLoad %float %49",
+ "%51 = OpAccessChain %_ptr_Function_float %param_1 %uint_1",
+ "%52 = OpLoad %float %51",
+ "%53 = OpFAdd %float %50 %52",
+ "OpStore %46 %53",
+ "%54 = OpAccessChain %_ptr_Function_float %param_1 %uint_2",
+ "%55 = OpLoad %float %54",
+ "OpStore %47 %55",
+ "%58 = OpLoad %float %46",
+ "%59 = OpLoad %float %47",
+ "%60 = OpFMul %float %58 %59",
+ "OpStore %57 %60",
+ "%56 = OpLoad %float %57",
+ "OpStore %48 %56",
+ "%30 = OpLoad %float %48",
+ "%31 = OpCompositeConstruct %v4float %30 %30 %30 %30",
+ "OpStore %color %31",
+ "%32 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %32",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, InOutParameter) {
+ // #version 400
+ //
+ // in vec4 Basecolor;
+ //
+ // void foo(inout vec4 bar)
+ // {
+ // bar.z = bar.x + bar.y;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 b = Basecolor;
+ // foo(b);
+ // vec4 color = vec4(b.z);
+ // gl_FragColor = color;
+ // }
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\" %Basecolor %gl_FragColor",
+ "OpExecutionMode %main OriginUpperLeft",
+ "OpSource GLSL 400",
+ "OpName %main \"main\"",
+ "OpName %foo_vf4_ \"foo(vf4;\"",
+ "OpName %bar \"bar\"",
+ "OpName %b \"b\"",
+ "OpName %Basecolor \"Basecolor\"",
+ "OpName %param \"param\"",
+ "OpName %color \"color\"",
+ "OpName %gl_FragColor \"gl_FragColor\"",
+ "%void = OpTypeVoid",
+ "%11 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+ "%15 = OpTypeFunction %void %_ptr_Function_v4float",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%uint_1 = OpConstant %uint 1",
+ "%uint_2 = OpConstant %uint 2",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%Basecolor = OpVariable %_ptr_Input_v4float Input",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+ "%foo_vf4_ = OpFunction %void None %15",
+ "%bar = OpFunctionParameter %_ptr_Function_v4float",
+ "%32 = OpLabel",
+ "%33 = OpAccessChain %_ptr_Function_float %bar %uint_0",
+ "%34 = OpLoad %float %33",
+ "%35 = OpAccessChain %_ptr_Function_float %bar %uint_1",
+ "%36 = OpLoad %float %35",
+ "%37 = OpFAdd %float %34 %36",
+ "%38 = OpAccessChain %_ptr_Function_float %bar %uint_2",
+ "OpStore %38 %37",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %11",
+ "%23 = OpLabel",
+ "%b = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%24 = OpLoad %v4float %Basecolor",
+ "OpStore %b %24",
+ "%25 = OpLoad %v4float %b",
+ "OpStore %param %25",
+ "%26 = OpFunctionCall %void %foo_vf4_ %param",
+ "%27 = OpLoad %v4float %param",
+ "OpStore %b %27",
+ "%28 = OpAccessChain %_ptr_Function_float %b %uint_2",
+ "%29 = OpLoad %float %28",
+ "%30 = OpCompositeConstruct %v4float %29 %29 %29 %29",
+ "OpStore %color %30",
+ "%31 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %31",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %11",
+ "%23 = OpLabel",
+ "%b = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%24 = OpLoad %v4float %Basecolor",
+ "OpStore %b %24",
+ "%25 = OpLoad %v4float %b",
+ "OpStore %param %25",
+ "%39 = OpAccessChain %_ptr_Function_float %param %uint_0",
+ "%40 = OpLoad %float %39",
+ "%41 = OpAccessChain %_ptr_Function_float %param %uint_1",
+ "%42 = OpLoad %float %41",
+ "%43 = OpFAdd %float %40 %42",
+ "%44 = OpAccessChain %_ptr_Function_float %param %uint_2",
+ "OpStore %44 %43",
+ "%27 = OpLoad %v4float %param",
+ "OpStore %b %27",
+ "%28 = OpAccessChain %_ptr_Function_float %b %uint_2",
+ "%29 = OpLoad %float %28",
+ "%30 = OpCompositeConstruct %v4float %29 %29 %29 %29",
+ "OpStore %color %30",
+ "%31 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %31",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, BranchInCallee) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // float r = bar.x;
+ // if (r < 0.0)
+ // r = -r;
+ // return r;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ //
+ // gl_FragColor = color;
+ // }
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "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 %foo_vf4_ \"foo(vf4;\"",
+ "OpName %bar \"bar\"",
+ "OpName %r \"r\"",
+ "OpName %color \"color\"",
+ "OpName %BaseColor \"BaseColor\"",
+ "OpName %param \"param\"",
+ "OpName %gl_FragColor \"gl_FragColor\"",
+ "%void = OpTypeVoid",
+ "%11 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+ "%15 = OpTypeFunction %float %_ptr_Function_v4float",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+ "%float_0 = OpConstant %float 0",
+ "%bool = OpTypeBool",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%BaseColor = OpVariable %_ptr_Input_v4float Input",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+ "%foo_vf4_ = OpFunction %float None %15",
+ "%bar = OpFunctionParameter %_ptr_Function_v4float",
+ "%28 = OpLabel",
+ "%r = OpVariable %_ptr_Function_float Function",
+ "%29 = OpAccessChain %_ptr_Function_float %bar %uint_0",
+ "%30 = OpLoad %float %29",
+ "OpStore %r %30",
+ "%31 = OpLoad %float %r",
+ "%32 = OpFOrdLessThan %bool %31 %float_0",
+ "OpSelectionMerge %33 None",
+ "OpBranchConditional %32 %34 %33",
+ "%34 = OpLabel",
+ "%35 = OpLoad %float %r",
+ "%36 = OpFNegate %float %35",
+ "OpStore %r %36",
+ "OpBranch %33",
+ "%33 = OpLabel",
+ "%37 = OpLoad %float %r",
+ "OpReturnValue %37",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %11",
+ "%23 = OpLabel",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%24 = OpLoad %v4float %BaseColor",
+ "OpStore %param %24",
+ "%25 = OpFunctionCall %float %foo_vf4_ %param",
+ "%26 = OpCompositeConstruct %v4float %25 %25 %25 %25",
+ "OpStore %color %26",
+ "%27 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %27",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %11",
+ "%23 = OpLabel",
+ "%38 = OpVariable %_ptr_Function_float Function",
+ "%39 = OpVariable %_ptr_Function_float Function",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%24 = OpLoad %v4float %BaseColor",
+ "OpStore %param %24",
+ "%40 = OpAccessChain %_ptr_Function_float %param %uint_0",
+ "%41 = OpLoad %float %40",
+ "OpStore %38 %41",
+ "%42 = OpLoad %float %38",
+ "%43 = OpFOrdLessThan %bool %42 %float_0",
+ "OpSelectionMerge %44 None",
+ "OpBranchConditional %43 %45 %44",
+ "%45 = OpLabel",
+ "%46 = OpLoad %float %38",
+ "%47 = OpFNegate %float %46",
+ "OpStore %38 %47",
+ "OpBranch %44",
+ "%44 = OpLabel",
+ "%48 = OpLoad %float %38",
+ "OpStore %39 %48",
+ "%25 = OpLoad %float %39",
+ "%26 = OpCompositeConstruct %v4float %25 %25 %25 %25",
+ "OpStore %color %26",
+ "%27 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %27",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, PhiAfterCall) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(float bar)
+ // {
+ // float r = bar;
+ // if (r < 0.0)
+ // r = -r;
+ // return r;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = BaseColor;
+ // if (foo(color.x) > 2.0 && foo(color.y) > 2.0)
+ // color = vec4(0.0);
+ // gl_FragColor = color;
+ // }
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "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 %foo_f1_ \"foo(f1;\"",
+ "OpName %bar \"bar\"",
+ "OpName %r \"r\"",
+ "OpName %color \"color\"",
+ "OpName %BaseColor \"BaseColor\"",
+ "OpName %param \"param\"",
+ "OpName %param_0 \"param\"",
+ "OpName %gl_FragColor \"gl_FragColor\"",
+ "%void = OpTypeVoid",
+ "%12 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%15 = OpTypeFunction %float %_ptr_Function_float",
+ "%float_0 = OpConstant %float 0",
+ "%bool = OpTypeBool",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%BaseColor = OpVariable %_ptr_Input_v4float Input",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+ "%float_2 = OpConstant %float 2",
+ "%uint_1 = OpConstant %uint 1",
+ "%25 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+ "%foo_f1_ = OpFunction %float None %15",
+ "%bar = OpFunctionParameter %_ptr_Function_float",
+ "%43 = OpLabel",
+ "%r = OpVariable %_ptr_Function_float Function",
+ "%44 = OpLoad %float %bar",
+ "OpStore %r %44",
+ "%45 = OpLoad %float %r",
+ "%46 = OpFOrdLessThan %bool %45 %float_0",
+ "OpSelectionMerge %47 None",
+ "OpBranchConditional %46 %48 %47",
+ "%48 = OpLabel",
+ "%49 = OpLoad %float %r",
+ "%50 = OpFNegate %float %49",
+ "OpStore %r %50",
+ "OpBranch %47",
+ "%47 = OpLabel",
+ "%51 = OpLoad %float %r",
+ "OpReturnValue %51",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %12",
+ "%27 = OpLabel",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_float Function",
+ "%param_0 = OpVariable %_ptr_Function_float Function",
+ "%28 = OpLoad %v4float %BaseColor",
+ "OpStore %color %28",
+ "%29 = OpAccessChain %_ptr_Function_float %color %uint_0",
+ "%30 = OpLoad %float %29",
+ "OpStore %param %30",
+ "%31 = OpFunctionCall %float %foo_f1_ %param",
+ "%32 = OpFOrdGreaterThan %bool %31 %float_2",
+ "OpSelectionMerge %33 None",
+ "OpBranchConditional %32 %34 %33",
+ "%34 = OpLabel",
+ "%35 = OpAccessChain %_ptr_Function_float %color %uint_1",
+ "%36 = OpLoad %float %35",
+ "OpStore %param_0 %36",
+ "%37 = OpFunctionCall %float %foo_f1_ %param_0",
+ "%38 = OpFOrdGreaterThan %bool %37 %float_2",
+ "OpBranch %33",
+ "%33 = OpLabel",
+ "%39 = OpPhi %bool %32 %27 %38 %34",
+ "OpSelectionMerge %40 None",
+ "OpBranchConditional %39 %41 %40",
+ "%41 = OpLabel",
+ "OpStore %color %25",
+ "OpBranch %40",
+ "%40 = OpLabel",
+ "%42 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %42",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %12",
+ "%27 = OpLabel",
+ "%62 = OpVariable %_ptr_Function_float Function",
+ "%63 = OpVariable %_ptr_Function_float Function",
+ "%52 = OpVariable %_ptr_Function_float Function",
+ "%53 = OpVariable %_ptr_Function_float Function",
+ "%color = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_float Function",
+ "%param_0 = OpVariable %_ptr_Function_float Function",
+ "%28 = OpLoad %v4float %BaseColor",
+ "OpStore %color %28",
+ "%29 = OpAccessChain %_ptr_Function_float %color %uint_0",
+ "%30 = OpLoad %float %29",
+ "OpStore %param %30",
+ "%54 = OpLoad %float %param",
+ "OpStore %52 %54",
+ "%55 = OpLoad %float %52",
+ "%56 = OpFOrdLessThan %bool %55 %float_0",
+ "OpSelectionMerge %57 None",
+ "OpBranchConditional %56 %58 %57",
+ "%58 = OpLabel",
+ "%59 = OpLoad %float %52",
+ "%60 = OpFNegate %float %59",
+ "OpStore %52 %60",
+ "OpBranch %57",
+ "%57 = OpLabel",
+ "%61 = OpLoad %float %52",
+ "OpStore %53 %61",
+ "%31 = OpLoad %float %53",
+ "%32 = OpFOrdGreaterThan %bool %31 %float_2",
+ "OpSelectionMerge %33 None",
+ "OpBranchConditional %32 %34 %33",
+ "%34 = OpLabel",
+ "%35 = OpAccessChain %_ptr_Function_float %color %uint_1",
+ "%36 = OpLoad %float %35",
+ "OpStore %param_0 %36",
+ "%64 = OpLoad %float %param_0",
+ "OpStore %62 %64",
+ "%65 = OpLoad %float %62",
+ "%66 = OpFOrdLessThan %bool %65 %float_0",
+ "OpSelectionMerge %67 None",
+ "OpBranchConditional %66 %68 %67",
+ "%68 = OpLabel",
+ "%69 = OpLoad %float %62",
+ "%70 = OpFNegate %float %69",
+ "OpStore %62 %70",
+ "OpBranch %67",
+ "%67 = OpLabel",
+ "%71 = OpLoad %float %62",
+ "OpStore %63 %71",
+ "%37 = OpLoad %float %63",
+ "%38 = OpFOrdGreaterThan %bool %37 %float_2",
+ "OpBranch %33",
+ "%33 = OpLabel",
+ "%39 = OpPhi %bool %32 %57 %38 %67",
+ "OpSelectionMerge %40 None",
+ "OpBranchConditional %39 %41 %40",
+ "%41 = OpLabel",
+ "OpStore %color %25",
+ "OpBranch %40",
+ "%40 = OpLabel",
+ "%42 = OpLoad %v4float %color",
+ "OpStore %gl_FragColor %42",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, OpSampledImageOutOfBlock) {
+ // #version 450
+ //
+ // uniform texture2D t2D;
+ // uniform sampler samp;
+ // out vec4 FragColor;
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // float r = bar.x;
+ // if (r < 0.0)
+ // r = -r;
+ // return r;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color1 = texture(sampler2D(t2D, samp), vec2(1.0));
+ // vec4 color2 = vec4(foo(BaseColor));
+ // vec4 color3 = texture(sampler2D(t2D, samp), vec2(0.5));
+ // FragColor = (color1 + color2 + color3)/3;
+ // }
+ //
+ // Note: the before SPIR-V will need to be edited to create a use of
+ // the OpSampledImage across the function call.
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\" %BaseColor %FragColor",
+ "OpExecutionMode %main OriginUpperLeft",
+ "OpSource GLSL 450",
+ "OpName %main \"main\"",
+ "OpName %foo_vf4_ \"foo(vf4;\"",
+ "OpName %bar \"bar\"",
+ "OpName %r \"r\"",
+ "OpName %color1 \"color1\"",
+ "OpName %t2D \"t2D\"",
+ "OpName %samp \"samp\"",
+ "OpName %color2 \"color2\"",
+ "OpName %BaseColor \"BaseColor\"",
+ "OpName %param \"param\"",
+ "OpName %color3 \"color3\"",
+ "OpName %FragColor \"FragColor\"",
+ "OpDecorate %t2D DescriptorSet 0",
+ "OpDecorate %samp DescriptorSet 0",
+ "%void = OpTypeVoid",
+ "%15 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+ "%19 = OpTypeFunction %float %_ptr_Function_v4float",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+ "%float_0 = OpConstant %float 0",
+ "%bool = OpTypeBool",
+ "%25 = OpTypeImage %float 2D 0 0 0 1 Unknown",
+"%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25",
+ "%t2D = OpVariable %_ptr_UniformConstant_25 UniformConstant",
+ "%27 = OpTypeSampler",
+"%_ptr_UniformConstant_27 = OpTypePointer UniformConstant %27",
+ "%samp = OpVariable %_ptr_UniformConstant_27 UniformConstant",
+ "%29 = OpTypeSampledImage %25",
+ "%v2float = OpTypeVector %float 2",
+ "%float_1 = OpConstant %float 1",
+ "%32 = OpConstantComposite %v2float %float_1 %float_1",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%BaseColor = OpVariable %_ptr_Input_v4float Input",
+ "%float_0_5 = OpConstant %float 0.5",
+ "%35 = OpConstantComposite %v2float %float_0_5 %float_0_5",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+ "%FragColor = OpVariable %_ptr_Output_v4float Output",
+ "%float_3 = OpConstant %float 3",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+ "%foo_vf4_ = OpFunction %float None %19",
+ "%bar = OpFunctionParameter %_ptr_Function_v4float",
+ "%56 = OpLabel",
+ "%r = OpVariable %_ptr_Function_float Function",
+ "%57 = OpAccessChain %_ptr_Function_float %bar %uint_0",
+ "%58 = OpLoad %float %57",
+ "OpStore %r %58",
+ "%59 = OpLoad %float %r",
+ "%60 = OpFOrdLessThan %bool %59 %float_0",
+ "OpSelectionMerge %61 None",
+ "OpBranchConditional %60 %62 %61",
+ "%62 = OpLabel",
+ "%63 = OpLoad %float %r",
+ "%64 = OpFNegate %float %63",
+ "OpStore %r %64",
+ "OpBranch %61",
+ "%61 = OpLabel",
+ "%65 = OpLoad %float %r",
+ "OpReturnValue %65",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %15",
+ "%38 = OpLabel",
+ "%color1 = OpVariable %_ptr_Function_v4float Function",
+ "%color2 = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color3 = OpVariable %_ptr_Function_v4float Function",
+ "%39 = OpLoad %25 %t2D",
+ "%40 = OpLoad %27 %samp",
+ "%41 = OpSampledImage %29 %39 %40",
+ "%42 = OpImageSampleImplicitLod %v4float %41 %32",
+ "OpStore %color1 %42",
+ "%43 = OpLoad %v4float %BaseColor",
+ "OpStore %param %43",
+ "%44 = OpFunctionCall %float %foo_vf4_ %param",
+ "%45 = OpCompositeConstruct %v4float %44 %44 %44 %44",
+ "OpStore %color2 %45",
+ "%46 = OpLoad %25 %t2D",
+ "%47 = OpLoad %27 %samp",
+ "%48 = OpImageSampleImplicitLod %v4float %41 %35",
+ "OpStore %color3 %48",
+ "%49 = OpLoad %v4float %color1",
+ "%50 = OpLoad %v4float %color2",
+ "%51 = OpFAdd %v4float %49 %50",
+ "%52 = OpLoad %v4float %color3",
+ "%53 = OpFAdd %v4float %51 %52",
+ "%54 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3",
+ "%55 = OpFDiv %v4float %53 %54",
+ "OpStore %FragColor %55",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %15",
+ "%38 = OpLabel",
+ "%66 = OpVariable %_ptr_Function_float Function",
+ "%67 = OpVariable %_ptr_Function_float Function",
+ "%color1 = OpVariable %_ptr_Function_v4float Function",
+ "%color2 = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color3 = OpVariable %_ptr_Function_v4float Function",
+ "%39 = OpLoad %25 %t2D",
+ "%40 = OpLoad %27 %samp",
+ "%41 = OpSampledImage %29 %39 %40",
+ "%42 = OpImageSampleImplicitLod %v4float %41 %32",
+ "OpStore %color1 %42",
+ "%43 = OpLoad %v4float %BaseColor",
+ "OpStore %param %43",
+ "%68 = OpAccessChain %_ptr_Function_float %param %uint_0",
+ "%69 = OpLoad %float %68",
+ "OpStore %66 %69",
+ "%70 = OpLoad %float %66",
+ "%71 = OpFOrdLessThan %bool %70 %float_0",
+ "OpSelectionMerge %72 None",
+ "OpBranchConditional %71 %73 %72",
+ "%73 = OpLabel",
+ "%74 = OpLoad %float %66",
+ "%75 = OpFNegate %float %74",
+ "OpStore %66 %75",
+ "OpBranch %72",
+ "%72 = OpLabel",
+ "%76 = OpLoad %float %66",
+ "OpStore %67 %76",
+ "%44 = OpLoad %float %67",
+ "%45 = OpCompositeConstruct %v4float %44 %44 %44 %44",
+ "OpStore %color2 %45",
+ "%46 = OpLoad %25 %t2D",
+ "%47 = OpLoad %27 %samp",
+ "%77 = OpSampledImage %29 %39 %40",
+ "%48 = OpImageSampleImplicitLod %v4float %77 %35",
+ "OpStore %color3 %48",
+ "%49 = OpLoad %v4float %color1",
+ "%50 = OpLoad %v4float %color2",
+ "%51 = OpFAdd %v4float %49 %50",
+ "%52 = OpLoad %v4float %color3",
+ "%53 = OpFAdd %v4float %51 %52",
+ "%54 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3",
+ "%55 = OpFDiv %v4float %53 %54",
+ "OpStore %FragColor %55",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, OpImageOutOfBlock) {
+ // #version 450
+ //
+ // uniform texture2D t2D;
+ // uniform sampler samp;
+ // uniform sampler samp2;
+ //
+ // out vec4 FragColor;
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // float r = bar.x;
+ // if (r < 0.0)
+ // r = -r;
+ // return r;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color1 = texture(sampler2D(t2D, samp), vec2(1.0));
+ // vec4 color2 = vec4(foo(BaseColor));
+ // vec4 color3 = texture(sampler2D(t2D, samp2), vec2(0.5));
+ // FragColor = (color1 + color2 + color3)/3;
+ // }
+ // Note: the before SPIR-V will need to be edited to create an OpImage
+ // from the first OpSampledImage, place it before the call and use it
+ // in the second OpSampledImage following the call.
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\" %BaseColor %FragColor",
+ "OpExecutionMode %main OriginUpperLeft",
+ "OpSource GLSL 450",
+ "OpName %main \"main\"",
+ "OpName %foo_vf4_ \"foo(vf4;\"",
+ "OpName %bar \"bar\"",
+ "OpName %r \"r\"",
+ "OpName %color1 \"color1\"",
+ "OpName %t2D \"t2D\"",
+ "OpName %samp \"samp\"",
+ "OpName %color2 \"color2\"",
+ "OpName %BaseColor \"BaseColor\"",
+ "OpName %param \"param\"",
+ "OpName %color3 \"color3\"",
+ "OpName %samp2 \"samp2\"",
+ "OpName %FragColor \"FragColor\"",
+ "OpDecorate %t2D DescriptorSet 0",
+ "OpDecorate %samp DescriptorSet 0",
+ "OpDecorate %samp2 DescriptorSet 0",
+ "%void = OpTypeVoid",
+ "%16 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+ "%20 = OpTypeFunction %float %_ptr_Function_v4float",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+ "%float_0 = OpConstant %float 0",
+ "%bool = OpTypeBool",
+ "%26 = OpTypeImage %float 2D 0 0 0 1 Unknown",
+"%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26",
+ "%t2D = OpVariable %_ptr_UniformConstant_26 UniformConstant",
+ "%28 = OpTypeSampler",
+"%_ptr_UniformConstant_28 = OpTypePointer UniformConstant %28",
+ "%samp = OpVariable %_ptr_UniformConstant_28 UniformConstant",
+ "%30 = OpTypeSampledImage %26",
+ "%v2float = OpTypeVector %float 2",
+ "%float_1 = OpConstant %float 1",
+ "%33 = OpConstantComposite %v2float %float_1 %float_1",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%BaseColor = OpVariable %_ptr_Input_v4float Input",
+ "%samp2 = OpVariable %_ptr_UniformConstant_28 UniformConstant",
+ "%float_0_5 = OpConstant %float 0.5",
+ "%36 = OpConstantComposite %v2float %float_0_5 %float_0_5",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+ "%FragColor = OpVariable %_ptr_Output_v4float Output",
+ "%float_3 = OpConstant %float 3",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+ "%foo_vf4_ = OpFunction %float None %20",
+ "%bar = OpFunctionParameter %_ptr_Function_v4float",
+ "%58 = OpLabel",
+ "%r = OpVariable %_ptr_Function_float Function",
+ "%59 = OpAccessChain %_ptr_Function_float %bar %uint_0",
+ "%60 = OpLoad %float %59",
+ "OpStore %r %60",
+ "%61 = OpLoad %float %r",
+ "%62 = OpFOrdLessThan %bool %61 %float_0",
+ "OpSelectionMerge %63 None",
+ "OpBranchConditional %62 %64 %63",
+ "%64 = OpLabel",
+ "%65 = OpLoad %float %r",
+ "%66 = OpFNegate %float %65",
+ "OpStore %r %66",
+ "OpBranch %63",
+ "%63 = OpLabel",
+ "%67 = OpLoad %float %r",
+ "OpReturnValue %67",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %16",
+ "%39 = OpLabel",
+ "%color1 = OpVariable %_ptr_Function_v4float Function",
+ "%color2 = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color3 = OpVariable %_ptr_Function_v4float Function",
+ "%40 = OpLoad %26 %t2D",
+ "%41 = OpLoad %28 %samp",
+ "%42 = OpSampledImage %30 %40 %41",
+ "%43 = OpImageSampleImplicitLod %v4float %42 %33",
+ "%44 = OpImage %26 %42",
+ "%45 = OpLoad %28 %samp2",
+ "OpStore %color1 %43",
+ "%46 = OpLoad %v4float %BaseColor",
+ "OpStore %param %46",
+ "%47 = OpFunctionCall %float %foo_vf4_ %param",
+ "%48 = OpCompositeConstruct %v4float %47 %47 %47 %47",
+ "OpStore %color2 %48",
+ "%49 = OpSampledImage %30 %44 %45",
+ "%50 = OpImageSampleImplicitLod %v4float %49 %36",
+ "OpStore %color3 %50",
+ "%51 = OpLoad %v4float %color1",
+ "%52 = OpLoad %v4float %color2",
+ "%53 = OpFAdd %v4float %51 %52",
+ "%54 = OpLoad %v4float %color3",
+ "%55 = OpFAdd %v4float %53 %54",
+ "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3",
+ "%57 = OpFDiv %v4float %55 %56",
+ "OpStore %FragColor %57",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %16",
+ "%39 = OpLabel",
+ "%68 = OpVariable %_ptr_Function_float Function",
+ "%69 = OpVariable %_ptr_Function_float Function",
+ "%color1 = OpVariable %_ptr_Function_v4float Function",
+ "%color2 = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color3 = OpVariable %_ptr_Function_v4float Function",
+ "%40 = OpLoad %26 %t2D",
+ "%41 = OpLoad %28 %samp",
+ "%42 = OpSampledImage %30 %40 %41",
+ "%43 = OpImageSampleImplicitLod %v4float %42 %33",
+ "%44 = OpImage %26 %42",
+ "%45 = OpLoad %28 %samp2",
+ "OpStore %color1 %43",
+ "%46 = OpLoad %v4float %BaseColor",
+ "OpStore %param %46",
+ "%70 = OpAccessChain %_ptr_Function_float %param %uint_0",
+ "%71 = OpLoad %float %70",
+ "OpStore %68 %71",
+ "%72 = OpLoad %float %68",
+ "%73 = OpFOrdLessThan %bool %72 %float_0",
+ "OpSelectionMerge %74 None",
+ "OpBranchConditional %73 %75 %74",
+ "%75 = OpLabel",
+ "%76 = OpLoad %float %68",
+ "%77 = OpFNegate %float %76",
+ "OpStore %68 %77",
+ "OpBranch %74",
+ "%74 = OpLabel",
+ "%78 = OpLoad %float %68",
+ "OpStore %69 %78",
+ "%47 = OpLoad %float %69",
+ "%48 = OpCompositeConstruct %v4float %47 %47 %47 %47",
+ "OpStore %color2 %48",
+ "%79 = OpSampledImage %30 %40 %41",
+ "%80 = OpImage %26 %79",
+ "%49 = OpSampledImage %30 %80 %45",
+ "%50 = OpImageSampleImplicitLod %v4float %49 %36",
+ "OpStore %color3 %50",
+ "%51 = OpLoad %v4float %color1",
+ "%52 = OpLoad %v4float %color2",
+ "%53 = OpFAdd %v4float %51 %52",
+ "%54 = OpLoad %v4float %color3",
+ "%55 = OpFAdd %v4float %53 %54",
+ "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3",
+ "%57 = OpFDiv %v4float %55 %56",
+ "OpStore %FragColor %57",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, OpImageAndOpSampledImageOutOfBlock) {
+ // #version 450
+ //
+ // uniform texture2D t2D;
+ // uniform sampler samp;
+ // uniform sampler samp2;
+ //
+ // out vec4 FragColor;
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // float r = bar.x;
+ // if (r < 0.0)
+ // r = -r;
+ // return r;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color1 = texture(sampler2D(t2D, samp), vec2(1.0));
+ // vec4 color2 = vec4(foo(BaseColor));
+ // vec4 color3 = texture(sampler2D(t2D, samp2), vec2(0.5));
+ // FragColor = (color1 + color2 + color3)/3;
+ // }
+ // Note: the before SPIR-V will need to be edited to create an OpImage
+ // and subsequent OpSampledImage that is used across the function call.
+ const std::vector<const char*> predefs = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\" %BaseColor %FragColor",
+ "OpExecutionMode %main OriginUpperLeft",
+ "OpSource GLSL 450",
+ "OpName %main \"main\"",
+ "OpName %foo_vf4_ \"foo(vf4;\"",
+ "OpName %bar \"bar\"",
+ "OpName %r \"r\"",
+ "OpName %color1 \"color1\"",
+ "OpName %t2D \"t2D\"",
+ "OpName %samp \"samp\"",
+ "OpName %color2 \"color2\"",
+ "OpName %BaseColor \"BaseColor\"",
+ "OpName %param \"param\"",
+ "OpName %color3 \"color3\"",
+ "OpName %samp2 \"samp2\"",
+ "OpName %FragColor \"FragColor\"",
+ "OpDecorate %t2D DescriptorSet 0",
+ "OpDecorate %samp DescriptorSet 0",
+ "OpDecorate %samp2 DescriptorSet 0",
+ "%void = OpTypeVoid",
+ "%16 = OpTypeFunction %void",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Function_v4float = OpTypePointer Function %v4float",
+ "%20 = OpTypeFunction %float %_ptr_Function_v4float",
+"%_ptr_Function_float = OpTypePointer Function %float",
+ "%uint = OpTypeInt 32 0",
+ "%uint_0 = OpConstant %uint 0",
+ "%float_0 = OpConstant %float 0",
+ "%bool = OpTypeBool",
+ "%26 = OpTypeImage %float 2D 0 0 0 1 Unknown",
+"%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26",
+ "%t2D = OpVariable %_ptr_UniformConstant_26 UniformConstant",
+ "%28 = OpTypeSampler",
+"%_ptr_UniformConstant_28 = OpTypePointer UniformConstant %28",
+ "%samp = OpVariable %_ptr_UniformConstant_28 UniformConstant",
+ "%30 = OpTypeSampledImage %26",
+ "%v2float = OpTypeVector %float 2",
+ "%float_1 = OpConstant %float 1",
+ "%33 = OpConstantComposite %v2float %float_1 %float_1",
+"%_ptr_Input_v4float = OpTypePointer Input %v4float",
+ "%BaseColor = OpVariable %_ptr_Input_v4float Input",
+ "%samp2 = OpVariable %_ptr_UniformConstant_28 UniformConstant",
+ "%float_0_5 = OpConstant %float 0.5",
+ "%36 = OpConstantComposite %v2float %float_0_5 %float_0_5",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+ "%FragColor = OpVariable %_ptr_Output_v4float Output",
+ "%float_3 = OpConstant %float 3",
+ // clang-format on
+ };
+
+ const std::vector<const char*> nonEntryFuncs = {
+ // clang-format off
+ "%foo_vf4_ = OpFunction %float None %20",
+ "%bar = OpFunctionParameter %_ptr_Function_v4float",
+ "%58 = OpLabel",
+ "%r = OpVariable %_ptr_Function_float Function",
+ "%59 = OpAccessChain %_ptr_Function_float %bar %uint_0",
+ "%60 = OpLoad %float %59",
+ "OpStore %r %60",
+ "%61 = OpLoad %float %r",
+ "%62 = OpFOrdLessThan %bool %61 %float_0",
+ "OpSelectionMerge %63 None",
+ "OpBranchConditional %62 %64 %63",
+ "%64 = OpLabel",
+ "%65 = OpLoad %float %r",
+ "%66 = OpFNegate %float %65",
+ "OpStore %r %66",
+ "OpBranch %63",
+ "%63 = OpLabel",
+ "%67 = OpLoad %float %r",
+ "OpReturnValue %67",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> before = {
+ // clang-format off
+ "%main = OpFunction %void None %16",
+ "%39 = OpLabel",
+ "%color1 = OpVariable %_ptr_Function_v4float Function",
+ "%color2 = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color3 = OpVariable %_ptr_Function_v4float Function",
+ "%40 = OpLoad %26 %t2D",
+ "%41 = OpLoad %28 %samp",
+ "%42 = OpSampledImage %30 %40 %41",
+ "%43 = OpImageSampleImplicitLod %v4float %42 %33",
+ "%44 = OpImage %26 %42",
+ "%45 = OpLoad %28 %samp2",
+ "%46 = OpSampledImage %30 %44 %45",
+ "OpStore %color1 %43",
+ "%47 = OpLoad %v4float %BaseColor",
+ "OpStore %param %47",
+ "%48 = OpFunctionCall %float %foo_vf4_ %param",
+ "%49 = OpCompositeConstruct %v4float %48 %48 %48 %48",
+ "OpStore %color2 %49",
+ "%50 = OpImageSampleImplicitLod %v4float %46 %36",
+ "OpStore %color3 %50",
+ "%51 = OpLoad %v4float %color1",
+ "%52 = OpLoad %v4float %color2",
+ "%53 = OpFAdd %v4float %51 %52",
+ "%54 = OpLoad %v4float %color3",
+ "%55 = OpFAdd %v4float %53 %54",
+ "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3",
+ "%57 = OpFDiv %v4float %55 %56",
+ "OpStore %FragColor %57",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ const std::vector<const char*> after = {
+ // clang-format off
+ "%main = OpFunction %void None %16",
+ "%39 = OpLabel",
+ "%68 = OpVariable %_ptr_Function_float Function",
+ "%69 = OpVariable %_ptr_Function_float Function",
+ "%color1 = OpVariable %_ptr_Function_v4float Function",
+ "%color2 = OpVariable %_ptr_Function_v4float Function",
+ "%param = OpVariable %_ptr_Function_v4float Function",
+ "%color3 = OpVariable %_ptr_Function_v4float Function",
+ "%40 = OpLoad %26 %t2D",
+ "%41 = OpLoad %28 %samp",
+ "%42 = OpSampledImage %30 %40 %41",
+ "%43 = OpImageSampleImplicitLod %v4float %42 %33",
+ "%44 = OpImage %26 %42",
+ "%45 = OpLoad %28 %samp2",
+ "%46 = OpSampledImage %30 %44 %45",
+ "OpStore %color1 %43",
+ "%47 = OpLoad %v4float %BaseColor",
+ "OpStore %param %47",
+ "%70 = OpAccessChain %_ptr_Function_float %param %uint_0",
+ "%71 = OpLoad %float %70",
+ "OpStore %68 %71",
+ "%72 = OpLoad %float %68",
+ "%73 = OpFOrdLessThan %bool %72 %float_0",
+ "OpSelectionMerge %74 None",
+ "OpBranchConditional %73 %75 %74",
+ "%75 = OpLabel",
+ "%76 = OpLoad %float %68",
+ "%77 = OpFNegate %float %76",
+ "OpStore %68 %77",
+ "OpBranch %74",
+ "%74 = OpLabel",
+ "%78 = OpLoad %float %68",
+ "OpStore %69 %78",
+ "%48 = OpLoad %float %69",
+ "%49 = OpCompositeConstruct %v4float %48 %48 %48 %48",
+ "OpStore %color2 %49",
+ "%79 = OpSampledImage %30 %40 %41",
+ "%80 = OpImage %26 %79",
+ "%81 = OpSampledImage %30 %80 %45",
+ "%50 = OpImageSampleImplicitLod %v4float %81 %36",
+ "OpStore %color3 %50",
+ "%51 = OpLoad %v4float %color1",
+ "%52 = OpLoad %v4float %color2",
+ "%53 = OpFAdd %v4float %51 %52",
+ "%54 = OpLoad %v4float %color3",
+ "%55 = OpFAdd %v4float %53 %54",
+ "%56 = OpCompositeConstruct %v4float %float_3 %float_3 %float_3 %float_3",
+ "%57 = OpFDiv %v4float %55 %56",
+ "OpStore %FragColor %57",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ JoinAllInsts(Concat(Concat(predefs, before), nonEntryFuncs)),
+ JoinAllInsts(Concat(Concat(predefs, after), nonEntryFuncs)),
+ /* skip_nop = */ false, /* do_validate = */ true);
+}
+
+TEST_F(InlineTest, EarlyReturnFunctionInlined) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // if (bar.x < 0.0)
+ // return 0.0;
+ // return bar.x;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+
+ 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 %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%foo_vf4_ = OpFunction %float None %14
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%27 = OpLabel
+%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%29 = OpLoad %float %28
+%30 = OpFOrdLessThan %bool %29 %float_0
+OpSelectionMerge %31 None
+OpBranchConditional %30 %32 %31
+%32 = OpLabel
+OpReturnValue %float_0
+%31 = OpLabel
+%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%34 = OpLoad %float %33
+OpReturnValue %34
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%22 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+%24 = OpFunctionCall %float %foo_vf4_ %param
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%false = OpConstantFalse %bool
+%main = OpFunction %void None %10
+%22 = OpLabel
+%35 = OpVariable %_ptr_Function_float Function
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+OpBranch %36
+%36 = OpLabel
+OpLoopMerge %37 %38 None
+OpBranch %39
+%39 = OpLabel
+%40 = OpAccessChain %_ptr_Function_float %param %uint_0
+%41 = OpLoad %float %40
+%42 = OpFOrdLessThan %bool %41 %float_0
+OpSelectionMerge %43 None
+OpBranchConditional %42 %44 %43
+%44 = OpLabel
+OpStore %35 %float_0
+OpBranch %37
+%43 = OpLabel
+%45 = OpAccessChain %_ptr_Function_float %param %uint_0
+%46 = OpLoad %float %45
+OpStore %35 %46
+OpBranch %37
+%38 = OpLabel
+OpBranchConditional %false %36 %37
+%37 = OpLabel
+%24 = OpLoad %float %35
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + before + nonEntryFuncs,
+ predefs + after + nonEntryFuncs,
+ false, true);
+}
+
+TEST_F(InlineTest, EarlyReturnNotAppearingLastInFunctionInlined) {
+ // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/755
+ //
+ // Original example is derived from:
+ //
+ // #version 450
+ //
+ // float foo() {
+ // if (true) {
+ // }
+ // }
+ //
+ // void main() { foo(); }
+ //
+ // But the order of basic blocks in foo is changed so that the return
+ // block is listed second-last. There is only one return in the callee
+ // but it does not appear last.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %foo_ "foo("
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%foo_ = OpFunction %void None %4
+%7 = OpLabel
+OpSelectionMerge %8 None
+OpBranchConditional %true %9 %8
+%8 = OpLabel
+OpReturn
+%9 = OpLabel
+OpBranch %8
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %4
+%10 = OpLabel
+%11 = OpFunctionCall %void %foo_
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %4
+%10 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %true %13 %12
+%12 = OpLabel
+OpBranch %14
+%13 = OpLabel
+OpBranch %12
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(InlineTest, ForwardReferencesInPhiInlined) {
+ // The basic structure of the test case is like this:
+ //
+ // int foo() {
+ // int result = 1;
+ // if (true) {
+ // result = 1;
+ // }
+ // return result;
+ // }
+ //
+ // void main() {
+ // int x = foo();
+ // }
+ //
+ // but with modifications: Using Phi instead of load/store, and the
+ // return block in foo appears before the "then" block.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %foo_ "foo("
+OpName %x "x"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%8 = OpTypeFunction %int
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int_0 = OpConstant %int 0
+%_ptr_Function_int = OpTypePointer Function %int
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%foo_ = OpFunction %int None %8
+%13 = OpLabel
+%14 = OpCopyObject %int %int_0
+OpSelectionMerge %15 None
+OpBranchConditional %true %16 %15
+%15 = OpLabel
+%17 = OpPhi %int %14 %13 %18 %16
+OpReturnValue %17
+%16 = OpLabel
+%18 = OpCopyObject %int %int_0
+OpBranch %15
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %6
+%19 = OpLabel
+%x = OpVariable %_ptr_Function_int Function
+%20 = OpFunctionCall %int %foo_
+OpStore %x %20
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %6
+%19 = OpLabel
+%21 = OpVariable %_ptr_Function_int Function
+%x = OpVariable %_ptr_Function_int Function
+%22 = OpCopyObject %int %int_0
+OpSelectionMerge %23 None
+OpBranchConditional %true %24 %23
+%23 = OpLabel
+%26 = OpPhi %int %22 %19 %25 %24
+OpStore %21 %26
+OpBranch %27
+%24 = OpLabel
+%25 = OpCopyObject %int %int_0
+OpBranch %23
+%27 = OpLabel
+%20 = OpLoad %int %21
+OpStore %x %20
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(InlineTest, EarlyReturnInLoopIsNotInlined) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // while (true) {
+ // if (bar.x < 0.0)
+ // return 0.0;
+ // return bar.x;
+ // }
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+
+ const std::string assembly =
+ 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 %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%23 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%24 = OpLoad %v4float %BaseColor
+OpStore %param %24
+%25 = OpFunctionCall %float %foo_vf4_ %param
+%26 = OpCompositeConstruct %v4float %25 %25 %25 %25
+OpStore %color %26
+%27 = OpLoad %v4float %color
+OpStore %gl_FragColor %27
+OpReturn
+OpFunctionEnd
+%foo_vf4_ = OpFunction %float None %14
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%28 = OpLabel
+OpBranch %29
+%29 = OpLabel
+OpLoopMerge %30 %31 None
+OpBranch %32
+%32 = OpLabel
+OpBranchConditional %true %33 %30
+%33 = OpLabel
+%34 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%35 = OpLoad %float %34
+%36 = OpFOrdLessThan %bool %35 %float_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
+OpReturnValue %float_0
+%37 = OpLabel
+%39 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%40 = OpLoad %float %39
+OpReturnValue %40
+%31 = OpLabel
+OpBranch %29
+%30 = OpLabel
+%41 = OpUndef %float
+OpReturnValue %41
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(assembly, assembly, false, true);
+}
+
+TEST_F(InlineTest, ExternalFunctionIsNotInlined) {
+ // In particular, don't crash.
+ // See report https://github.com/KhronosGroup/SPIRV-Tools/issues/605
+ const std::string assembly =
+ R"(OpCapability Addresses
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Physical32 OpenCL
+OpEntryPoint Kernel %1 "entry_pt"
+OpDecorate %2 LinkageAttributes "external" Import
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%2 = OpFunction %void None %4
+OpFunctionEnd
+%1 = OpFunction %void None %4
+%5 = OpLabel
+%6 = OpFunctionCall %void %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(assembly, assembly, false, true);
+}
+
+TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCallee) {
+ // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/787
+ //
+ // CFG structure is:
+ // foo:
+ // fooentry -> fooexit
+ //
+ // main:
+ // entry -> loop
+ // loop -> loop, merge
+ // loop calls foo()
+ // merge
+ //
+ // Since the callee has multiple blocks, it will split the calling block
+ // into at least two, resulting in a new "back-half" block that contains
+ // the instructions after the inlined function call. If the calling block
+ // has an OpLoopMerge that points back to the calling block itself, then
+ // the OpLoopMerge can't remain in the back-half block, but must be
+ // moved to the end of the original calling block, and it continue target
+ // operand updated to point to the back-half block.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%void = OpTypeVoid
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%5 = OpTypeFunction %void
+%6 = OpFunction %void None %5
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%1 = OpFunction %void None %5
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+%11 = OpFunctionCall %void %6
+OpLoopMerge %12 %10 None
+OpBranchConditional %true %10 %12
+%12 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%1 = OpFunction %void None %5
+%9 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %12 %13 None
+OpBranch %13
+%13 = OpLabel
+OpBranchConditional %true %10 %12
+%12 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(InlineTest, MultiBlockLoopHeaderCallsMultiBlockCallee) {
+ // Like SingleBlockLoopCallsMultiBlockCallee but the loop has several
+ // blocks, but the function call still occurs in the loop header.
+ // Example from https://github.com/KhronosGroup/SPIRV-Tools/issues/800
+
+ const std::string predefs =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%int_5 = OpConstant %int 5
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%12 = OpFunction %void None %11
+%13 = OpLabel
+%14 = OpCopyObject %int %int_1
+OpBranch %15
+%15 = OpLabel
+%16 = OpCopyObject %int %int_2
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+%20 = OpFunctionCall %void %12
+%21 = OpCopyObject %int %int_4
+OpLoopMerge %22 %23 None
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+%25 = OpCopyObject %int %int_1
+OpLoopMerge %22 %23 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpCopyObject %int %int_2
+%21 = OpCopyObject %int %int_4
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(InlineTest, SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge) {
+ // This is similar to SingleBlockLoopCallsMultiBlockCallee except
+ // that calleee block also has a merge instruction in its first block.
+ // That merge instruction must be an OpSelectionMerge (because the entry
+ // block of a function can't be the header of a loop since the entry
+ // block can't be the target of a branch).
+ //
+ // In this case the OpLoopMerge can't be placed in the same block as
+ // the OpSelectionMerge, so inlining must create a new block to contain
+ // the callee contents.
+ //
+ // Additionally, we have two dummy OpCopyObject instructions to prove that
+ // the OpLoopMerge is moved to the right location.
+ //
+ // Also ensure that OpPhis within the cloned callee code are valid.
+ // We need to test that the predecessor blocks are remapped correctly so that
+ // dominance rules are satisfied
+
+ const std::string predefs =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+)";
+
+ // This callee has multiple blocks, and an OpPhi in the last block
+ // that references a value from the first block. This tests that
+ // cloned block IDs are remapped appropriately. The OpPhi dominance
+ // requires that the remapped %9 must be in a block that dominates
+ // the remapped %8.
+ const std::string nonEntryFuncs =
+ R"(%7 = OpFunction %void None %6
+%8 = OpLabel
+%9 = OpCopyObject %bool %true
+OpSelectionMerge %10 None
+OpBranchConditional %true %10 %10
+%10 = OpLabel
+%11 = OpPhi %bool %9 %8
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%1 = OpFunction %void None %6
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+%14 = OpCopyObject %bool %false
+%15 = OpFunctionCall %void %7
+OpLoopMerge %16 %13 None
+OpBranchConditional %true %13 %16
+%16 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // Note the remapped Phi uses %17 as the parent instead
+ // of %13, demonstrating that the parent block has been remapped
+ // correctly.
+ const std::string after =
+ R"(%1 = OpFunction %void None %6
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+%14 = OpCopyObject %bool %false
+OpLoopMerge %16 %19 None
+OpBranch %17
+%17 = OpLabel
+%18 = OpCopyObject %bool %true
+OpSelectionMerge %19 None
+OpBranchConditional %true %19 %19
+%19 = OpLabel
+%20 = OpPhi %bool %18 %17
+OpBranchConditional %true %13 %16
+%16 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(InlineTest,
+ MultiBlockLoopHeaderCallsFromToMultiBlockCalleeHavingSelectionMerge) {
+ // This is similar to SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge
+ // but the call is in the header block of a multi block loop.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%int_5 = OpConstant %int 5
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%12 = OpFunction %void None %11
+%13 = OpLabel
+%14 = OpCopyObject %int %int_1
+OpSelectionMerge %15 None
+OpBranchConditional %true %15 %15
+%15 = OpLabel
+%16 = OpCopyObject %int %int_2
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+%20 = OpFunctionCall %void %12
+%21 = OpCopyObject %int %int_4
+OpLoopMerge %22 %23 None
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%1 = OpFunction %void None %11
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpCopyObject %int %int_3
+OpLoopMerge %22 %23 None
+OpBranch %25
+%25 = OpLabel
+%26 = OpCopyObject %int %int_1
+OpSelectionMerge %27 None
+OpBranchConditional %true %27 %27
+%27 = OpLabel
+%28 = OpCopyObject %int %int_2
+%21 = OpCopyObject %int %int_4
+OpBranchConditional %true %23 %22
+%23 = OpLabel
+%24 = OpCopyObject %int %int_5
+OpBranchConditional %true %18 %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(
+ InlineTest,
+ SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMergeAndMultiReturns) {
+ // This is similar to SingleBlockLoopCallsMultiBlockCalleeHavingSelectionMerge
+ // except that in addition to starting with a selection header, the
+ // callee also has multi returns.
+ //
+ // So now we have to accommodate:
+ // - The caller's OpLoopMerge (which must move to the first block)
+ // - The single-trip loop to wrap the multi returns, and
+ // - The callee's selection merge in its first block.
+ // Each of these must go into their own blocks.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%bool = OpTypeBool
+%int = OpTypeInt 32 1
+%true = OpConstantTrue %bool
+%false = OpConstantFalse %bool
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%13 = OpFunction %void None %12
+%14 = OpLabel
+%15 = OpCopyObject %int %int_0
+OpReturn
+%16 = OpLabel
+%17 = OpCopyObject %int %int_1
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%1 = OpFunction %void None %12
+%18 = OpLabel
+OpBranch %19
+%19 = OpLabel
+%20 = OpCopyObject %int %int_2
+%21 = OpFunctionCall %void %13
+%22 = OpCopyObject %int %int_3
+OpLoopMerge %23 %19 None
+OpBranchConditional %true %19 %23
+%23 = OpLabel
+%24 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%1 = OpFunction %void None %12
+%18 = OpLabel
+OpBranch %19
+%19 = OpLabel
+%20 = OpCopyObject %int %int_2
+%25 = OpCopyObject %int %int_0
+OpLoopMerge %23 %26 None
+OpBranch %26
+%27 = OpLabel
+%28 = OpCopyObject %int %int_1
+OpBranch %26
+%26 = OpLabel
+%22 = OpCopyObject %int %int_3
+OpBranchConditional %true %19 %23
+%23 = OpLabel
+%24 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(InlineTest, CalleeWithMultiReturnAndPhiRequiresEntryBlockRemapping) {
+ // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/790
+ //
+ // The callee has multiple returns, and so must be wrapped with a single-trip
+ // loop. That code must remap the callee entry block ID to the introduced
+ // loop body's ID. Otherwise you can get a dominance error in a cloned OpPhi.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource OpenCL_C 120
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_3 = OpConstant %int 3
+%int_4 = OpConstant %int 4
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+)";
+
+ // This callee has multiple returns, and a Phi in the second block referencing
+ // a value generated in the entry block.
+ const std::string nonEntryFuncs =
+ R"(%12 = OpFunction %void None %9
+%13 = OpLabel
+%14 = OpCopyObject %int %int_0
+OpBranch %15
+%15 = OpLabel
+%16 = OpPhi %int %14 %13
+%17 = OpCopyObject %int %int_1
+OpReturn
+%18 = OpLabel
+%19 = OpCopyObject %int %int_2
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%1 = OpFunction %void None %9
+%20 = OpLabel
+%21 = OpCopyObject %int %int_3
+%22 = OpFunctionCall %void %12
+%23 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%1 = OpFunction %void None %9
+%20 = OpLabel
+%21 = OpCopyObject %int %int_3
+%24 = OpCopyObject %int %int_0
+OpBranch %25
+%25 = OpLabel
+%26 = OpPhi %int %24 %20
+%27 = OpCopyObject %int %int_1
+OpBranch %28
+%29 = OpLabel
+%30 = OpCopyObject %int %int_2
+OpBranch %28
+%28 = OpLabel
+%23 = OpCopyObject %int %int_4
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + nonEntryFuncs + before,
+ predefs + nonEntryFuncs + after,
+ false, true);
+}
+
+TEST_F(InlineTest, NonInlinableCalleeWithSingleReturn) {
+ // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/2018
+ //
+ // The callee has a single return, but cannot be inlined because the
+ // return is inside a loop.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %_GLF_color
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %f_ "f("
+OpName %i "i"
+OpName %_GLF_color "_GLF_color"
+OpDecorate %_GLF_color Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%9 = OpTypeFunction %float
+%float_1 = OpConstant %float 1
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_GLF_color = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%20 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%21 = OpConstantComposite %v4float %float_0 %float_1 %float_0 %float_1
+)";
+
+ const std::string caller =
+ R"(%main = OpFunction %void None %7
+%22 = OpLabel
+%i = OpVariable %_ptr_Function_int Function
+OpStore %i %int_0
+OpBranch %23
+%23 = OpLabel
+OpLoopMerge %24 %25 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpLoad %int %i
+%28 = OpSLessThan %bool %27 %int_1
+OpBranchConditional %28 %29 %24
+%29 = OpLabel
+OpStore %_GLF_color %20
+%30 = OpFunctionCall %float %f_
+OpBranch %25
+%25 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpIAdd %int %31 %int_1
+OpStore %i %32
+OpBranch %23
+%24 = OpLabel
+OpStore %_GLF_color %21
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string callee =
+ R"(%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
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ predefs + caller + callee, predefs + caller + callee, false, true);
+}
+
+TEST_F(InlineTest, CalleeWithSingleReturnNeedsSingleTripLoopWrapper) {
+ // The case from https://github.com/KhronosGroup/SPIRV-Tools/issues/2018
+ //
+ // The callee has a single return, but needs single-trip loop wrapper
+ // to be inlined because the return is in a selection structure.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %_GLF_color
+OpExecutionMode %main OriginUpperLeft
+OpSource ESSL 310
+OpName %main "main"
+OpName %f_ "f("
+OpName %i "i"
+OpName %_GLF_color "_GLF_color"
+OpDecorate %_GLF_color Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%9 = OpTypeFunction %float
+%float_1 = OpConstant %float 1
+%bool = OpTypeBool
+%false = OpConstantFalse %bool
+%true = OpConstantTrue %bool
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_GLF_color = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+%21 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%22 = OpConstantComposite %v4float %float_0 %float_1 %float_0 %float_1
+)";
+
+ const std::string new_predefs =
+ R"(%_ptr_Function_float = OpTypePointer Function %float
+)";
+
+ const std::string main_before =
+ R"(%main = OpFunction %void None %7
+%23 = OpLabel
+%i = OpVariable %_ptr_Function_int Function
+OpStore %i %int_0
+OpBranch %24
+%24 = OpLabel
+OpLoopMerge %25 %26 None
+OpBranch %27
+%27 = OpLabel
+%28 = OpLoad %int %i
+%29 = OpSLessThan %bool %28 %int_1
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+OpStore %_GLF_color %21
+%31 = OpFunctionCall %float %f_
+OpBranch %26
+%26 = OpLabel
+%32 = OpLoad %int %i
+%33 = OpIAdd %int %32 %int_1
+OpStore %i %33
+OpBranch %24
+%25 = OpLabel
+OpStore %_GLF_color %22
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string main_after =
+ R"(%main = OpFunction %void None %7
+%23 = OpLabel
+%38 = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %i %int_0
+OpBranch %24
+%24 = OpLabel
+OpLoopMerge %25 %26 None
+OpBranch %27
+%27 = OpLabel
+%28 = OpLoad %int %i
+%29 = OpSLessThan %bool %28 %int_1
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+OpStore %_GLF_color %21
+OpBranch %39
+%39 = OpLabel
+OpLoopMerge %40 %41 None
+OpBranch %42
+%42 = OpLabel
+OpSelectionMerge %43 None
+OpBranchConditional %true %44 %43
+%44 = OpLabel
+OpStore %38 %float_1
+OpBranch %40
+%43 = OpLabel
+OpUnreachable
+%41 = OpLabel
+OpBranchConditional %false %39 %40
+%40 = OpLabel
+%31 = OpLoad %float %38
+OpBranch %26
+%26 = OpLabel
+%32 = OpLoad %int %i
+%33 = OpIAdd %int %32 %int_1
+OpStore %i %33
+OpBranch %24
+%25 = OpLabel
+OpStore %_GLF_color %22
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string callee =
+ R"(%f_ = OpFunction %float None %9
+%34 = OpLabel
+OpSelectionMerge %35 None
+OpBranchConditional %true %36 %35
+%36 = OpLabel
+OpReturnValue %float_1
+%35 = OpLabel
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(
+ predefs + main_before + callee,
+ predefs + new_predefs + main_after + callee, false, true);
+}
+
+TEST_F(InlineTest, Decorated1) {
+ // Same test as Simple with the difference
+ // that OpFAdd in the outlined function is
+ // decorated with RelaxedPrecision
+ // Expected result is an equal decoration
+ // of the corresponding inlined instruction
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // return bar.x + bar.y;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+
+ 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 %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %9 RelaxedPrecision
+)";
+
+ const std::string before =
+ R"(%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%15 = OpTypeFunction %float %_ptr_Function_v4float
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %11
+%22 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+%24 = OpFunctionCall %float %foo_vf4_ %param
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpDecorate %37 RelaxedPrecision
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%15 = OpTypeFunction %float %_ptr_Function_v4float
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %11
+%22 = OpLabel
+%32 = OpVariable %_ptr_Function_float Function
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+%33 = OpAccessChain %_ptr_Function_float %param %uint_0
+%34 = OpLoad %float %33
+%35 = OpAccessChain %_ptr_Function_float %param %uint_1
+%36 = OpLoad %float %35
+%37 = OpFAdd %float %34 %36
+OpStore %32 %37
+%24 = OpLoad %float %32
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%foo_vf4_ = OpFunction %float None %15
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%27 = OpLabel
+%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%29 = OpLoad %float %28
+%30 = OpAccessChain %_ptr_Function_float %bar %uint_1
+%31 = OpLoad %float %30
+%9 = OpFAdd %float %29 %31
+OpReturnValue %9
+OpFunctionEnd
+)";
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + before + nonEntryFuncs,
+ predefs + after + nonEntryFuncs,
+ false, true);
+}
+
+TEST_F(InlineTest, Decorated2) {
+ // Same test as Simple with the difference
+ // that the Result <id> of the outlined OpFunction
+ // is decorated with RelaxedPrecision
+ // Expected result is an equal decoration
+ // of the created return variable
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // float foo(vec4 bar)
+ // {
+ // return bar.x + bar.y;
+ // }
+ //
+ // void main()
+ // {
+ // vec4 color = vec4(foo(BaseColor));
+ // gl_FragColor = color;
+ // }
+
+ 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 %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %foo_vf4_ RelaxedPrecision
+)";
+
+ const std::string before =
+ R"(%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%21 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %BaseColor
+OpStore %param %22
+%23 = OpFunctionCall %float %foo_vf4_ %param
+%24 = OpCompositeConstruct %v4float %23 %23 %23 %23
+OpStore %color %24
+%25 = OpLoad %v4float %color
+OpStore %gl_FragColor %25
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpDecorate %32 RelaxedPrecision
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%uint_1 = OpConstant %uint 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%21 = OpLabel
+%32 = OpVariable %_ptr_Function_float Function
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %BaseColor
+OpStore %param %22
+%33 = OpAccessChain %_ptr_Function_float %param %uint_0
+%34 = OpLoad %float %33
+%35 = OpAccessChain %_ptr_Function_float %param %uint_1
+%36 = OpLoad %float %35
+%37 = OpFAdd %float %34 %36
+OpStore %32 %37
+%23 = OpLoad %float %32
+%24 = OpCompositeConstruct %v4float %23 %23 %23 %23
+OpStore %color %24
+%25 = OpLoad %v4float %color
+OpStore %gl_FragColor %25
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string nonEntryFuncs =
+ R"(%foo_vf4_ = OpFunction %float None %14
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+%27 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%28 = OpLoad %float %27
+%29 = OpAccessChain %_ptr_Function_float %bar %uint_1
+%30 = OpLoad %float %29
+%31 = OpFAdd %float %28 %30
+OpReturnValue %31
+OpFunctionEnd
+)";
+ SinglePassRunAndCheck<InlineExhaustivePass>(predefs + before + nonEntryFuncs,
+ predefs + after + nonEntryFuncs,
+ false, true);
+}
+
+TEST_F(InlineTest, DeleteName) {
+ // Test that the name of the result id of the call is deleted.
+ const std::string before =
+ R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ OpName %main_entry "main_entry"
+ OpName %foo_result "foo_result"
+ OpName %void_fn "void_fn"
+ OpName %foo "foo"
+ OpName %foo_entry "foo_entry"
+ %void = OpTypeVoid
+ %void_fn = OpTypeFunction %void
+ %foo = OpFunction %void None %void_fn
+ %foo_entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %void_fn
+ %main_entry = OpLabel
+ %foo_result = OpFunctionCall %void %foo
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+OpName %main_entry "main_entry"
+OpName %void_fn "void_fn"
+OpName %foo "foo"
+OpName %foo_entry "foo_entry"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%foo = OpFunction %void None %void_fn
+%foo_entry = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(before, after, false, true);
+}
+
+TEST_F(InlineTest, SetParent) {
+ // Test that after inlining all basic blocks have the correct parent.
+ const std::string text =
+ R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ OpName %main_entry "main_entry"
+ OpName %foo_result "foo_result"
+ OpName %void_fn "void_fn"
+ OpName %foo "foo"
+ OpName %foo_entry "foo_entry"
+ %void = OpTypeVoid
+ %void_fn = OpTypeFunction %void
+ %foo = OpFunction %void None %void_fn
+ %foo_entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %void_fn
+ %main_entry = OpLabel
+ %foo_result = OpFunctionCall %void %foo
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ InlineExhaustivePass pass;
+ pass.Run(context.get());
+
+ for (Function& func : *context->module()) {
+ for (BasicBlock& bb : func) {
+ EXPECT_TRUE(bb.GetParent() == &func);
+ }
+ }
+}
+
+TEST_F(InlineTest, OpKill) {
+ const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpKill
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%voidfuncty = OpTypeFunction %void
+%main = OpFunction %void None %voidfuncty
+%1 = OpLabel
+%2 = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+%func = OpFunction %void None %voidfuncty
+%3 = OpLabel
+OpKill
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
+TEST_F(InlineTest, OpKillWithTrailingInstructions) {
+ const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: [[var:%\w+]] = OpVariable
+; CHECK-NEXT: OpKill
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpStore [[var]]
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%bool_func_ptr = OpTypePointer Function %bool
+%voidfuncty = OpTypeFunction %void
+%main = OpFunction %void None %voidfuncty
+%1 = OpLabel
+%2 = OpVariable %bool_func_ptr Function
+%3 = OpFunctionCall %void %func
+OpStore %2 %true
+OpReturn
+OpFunctionEnd
+%func = OpFunction %void None %voidfuncty
+%4 = OpLabel
+OpKill
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
+TEST_F(InlineTest, OpKillInIf) {
+ const std::string text = R"(
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK-NEXT: [[ld:%\w+]] = OpLoad {{%\w+}} [[var]]
+; CHECK-NEXT: OpBranch [[label:%\w+]]
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: OpLoopMerge [[loop_merge:%\w+]] [[continue:%\w+]] None
+; CHECK-NEXT: OpBranch [[label:%\w+]]
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge [[sel_merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[kill_label:%\w+]] [[label:%\w+]]
+; CHECK-NEXT: [[kill_label]] = OpLabel
+; CHECK-NEXT: OpKill
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop_merge]]
+; CHECK-NEXT: [[sel_merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop_merge]]
+; CHECK-NEXT: [[continue]] = OpLabel
+; CHECK-NEXT: OpBranchConditional
+; CHECK-NEXT: [[loop_merge]] = OpLabel
+; CHECK-NEXT: OpStore [[var]] [[ld]]
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%bool_func_ptr = OpTypePointer Function %bool
+%voidfuncty = OpTypeFunction %void
+%main = OpFunction %void None %voidfuncty
+%1 = OpLabel
+%2 = OpVariable %bool_func_ptr Function
+%3 = OpLoad %bool %2
+%4 = OpFunctionCall %void %func
+OpStore %2 %3
+OpReturn
+OpFunctionEnd
+%func = OpFunction %void None %voidfuncty
+%5 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %true %7 %8
+%7 = OpLabel
+OpKill
+%8 = OpLabel
+OpReturn
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
+TEST_F(InlineTest, OpKillInLoop) {
+ const std::string text = R"(
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK-NEXT: [[ld:%\w+]] = OpLoad {{%\w+}} [[var]]
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK-NEXT: [[loop]] = OpLabel
+; CHECK-NEXT: OpLoopMerge [[loop_merge:%\w+]] [[continue:%\w+]] None
+; CHECK-NEXT: OpBranch [[label:%\w+]]
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: OpKill
+; CHECK-NEXT: [[loop_merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[label:%\w+]]
+; CHECK-NEXT: [[continue]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop]]
+; CHECK-NEXT: [[label]] = OpLabel
+; CHECK-NEXT: OpStore [[var]] [[ld]]
+; CHECK-NEXT: OpReturn
+; CHECK-NEXT: OpFunctionEnd
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%voidfuncty = OpTypeFunction %void
+%bool_func_ptr = OpTypePointer Function %bool
+%main = OpFunction %void None %voidfuncty
+%1 = OpLabel
+%2 = OpVariable %bool_func_ptr Function
+%3 = OpLoad %bool %2
+%4 = OpFunctionCall %void %func
+OpStore %2 %3
+OpReturn
+OpFunctionEnd
+%func = OpFunction %void None %voidfuncty
+%5 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpLoopMerge %6 %7 None
+OpBranch %8
+%8 = OpLabel
+OpKill
+%6 = OpLabel
+OpReturn
+%7 = OpLabel
+OpBranch %10
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
+TEST_F(InlineTest, OpVariableWithInit) {
+ // Check that there is a store that corresponds to the initializer. This
+ // test makes sure that is a store to the variable in the loop and before any
+ // load.
+ const std::string text = R"(
+; CHECK: OpFunction
+; CHECK-NOT: OpFunctionEnd
+; CHECK: [[var:%\w+]] = OpVariable %_ptr_Function_float Function %float_0
+; CHECK: OpLoopMerge [[outer_merge:%\w+]]
+; CHECK-NOT: OpLoad %float [[var]]
+; CHECK: OpStore [[var]] %float_0
+; CHECK: OpFunctionEnd
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %o
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpDecorate %o Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %7 = OpTypeFunction %float
+%_ptr_Function_float = OpTypePointer Function %float
+ %float_0 = OpConstant %float 0
+ %bool = OpTypeBool
+ %float_1 = OpConstant %float 1
+%_ptr_Output_float = OpTypePointer Output %float
+ %o = OpVariable %_ptr_Output_float Output
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpStore %o %float_0
+ OpBranch %34
+ %34 = OpLabel
+ %39 = OpPhi %int %int_0 %5 %47 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %41 = OpSLessThan %bool %39 %int_2
+ OpBranchConditional %41 %35 %36
+ %35 = OpLabel
+ %42 = OpFunctionCall %float %foo_
+ %43 = OpLoad %float %o
+ %44 = OpFAdd %float %43 %42
+ OpStore %o %44
+ OpBranch %37
+ %37 = OpLabel
+ %47 = OpIAdd %int %39 %int_1
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %foo_ = OpFunction %float None %7
+ %9 = OpLabel
+ %n = OpVariable %_ptr_Function_float Function %float_0
+ %13 = OpLoad %float %n
+ %15 = OpFOrdEqual %bool %13 %float_0
+ OpSelectionMerge %17 None
+ OpBranchConditional %15 %16 %17
+ %16 = OpLabel
+ %19 = OpLoad %float %n
+ %20 = OpFAdd %float %19 %float_1
+ OpStore %n %20
+ OpBranch %17
+ %17 = OpLabel
+ %21 = OpLoad %float %n
+ OpReturnValue %21
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
+TEST_F(InlineTest, DontInlineDirectlyRecursiveFunc) {
+ // Test that the name of the result id of the call is deleted.
+ const std::string test =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+OpDecorate %2 DescriptorSet 439418829
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
+}
+
+TEST_F(InlineTest, DontInlineInDirectlyRecursiveFunc) {
+ // Test that the name of the result id of the call is deleted.
+ const std::string test =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+OpDecorate %2 DescriptorSet 439418829
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_struct_6 = OpTypeStruct %float %float
+%7 = OpTypeFunction %_struct_6
+%1 = OpFunction %void Pure|Const %4
+%8 = OpLabel
+%2 = OpFunctionCall %_struct_6 %9
+OpKill
+OpFunctionEnd
+%9 = OpFunction %_struct_6 None %7
+%10 = OpLabel
+%11 = OpFunctionCall %_struct_6 %12
+OpUnreachable
+OpFunctionEnd
+%12 = OpFunction %_struct_6 None %7
+%13 = OpLabel
+%14 = OpFunctionCall %_struct_6 %9
+OpUnreachable
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// Empty modules
+// Modules without function definitions
+// Modules in which all functions do not call other functions
+// Caller and callee both accessing the same global variable
+// Functions with OpLine & OpNoLine
+// Others?
+
+// TODO(dneto): Test suggestions from code review
+// https://github.com/KhronosGroup/SPIRV-Tools/pull/534
+//
+// Callee function returns a value generated outside the callee,
+// e.g. a constant value. This might exercise some logic not yet
+// exercised by the current tests: the false branch in the "if"
+// inside the SpvOpReturnValue case in InlinePass::GenInlineCode?
+// SampledImage before function call, but callee is only single block.
+// Then the SampledImage instruction is not cloned. Documents existing
+// behaviour.
+// SampledImage after function call. It is not cloned or changed.
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/insert_extract_elim_test.cpp b/test/opt/insert_extract_elim_test.cpp
new file mode 100644
index 0000000..c516975
--- /dev/null
+++ b/test/opt/insert_extract_elim_test.cpp
@@ -0,0 +1,900 @@
+// 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 "source/opt/simplification_pass.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using InsertExtractElimTest = PassTest<::testing::Test>;
+
+TEST_F(InsertExtractElimTest, Simple) {
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // gl_FragColor = s0.v1;
+ // }
+
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%17 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+%19 = OpLoad %S_t %s0
+%20 = OpCompositeInsert %S_t %18 %19 1
+OpStore %s0 %20
+%21 = OpCompositeExtract %v4float %20 1
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%17 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+%19 = OpLoad %S_t %s0
+%20 = OpCompositeInsert %S_t %18 %19 1
+OpStore %s0 %20
+OpStore %gl_FragColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(predefs + before, predefs + after,
+ true, true);
+}
+
+TEST_F(InsertExtractElimTest, OptimizeAcrossNonConflictingInsert) {
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // s0.v0[2] = 0.0;
+ // gl_FragColor = s0.v1;
+ // }
+
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%float_0 = OpConstant %float 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%18 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%19 = OpLoad %v4float %BaseColor
+%20 = OpLoad %S_t %s0
+%21 = OpCompositeInsert %S_t %19 %20 1
+%22 = OpCompositeInsert %S_t %float_0 %21 0 2
+OpStore %s0 %22
+%23 = OpCompositeExtract %v4float %22 1
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%18 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%19 = OpLoad %v4float %BaseColor
+%20 = OpLoad %S_t %s0
+%21 = OpCompositeInsert %S_t %19 %20 1
+%22 = OpCompositeInsert %S_t %float_0 %21 0 2
+OpStore %s0 %22
+OpStore %gl_FragColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(predefs + before, predefs + after,
+ true, true);
+}
+
+TEST_F(InsertExtractElimTest, OptimizeOpaque) {
+ // SPIR-V not representable in GLSL; not generatable from HLSL
+ // for the moment.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %outColor %texCoords
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpMemberName %S_t 2 "smp"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %s0 "s0"
+OpName %texCoords "texCoords"
+OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%14 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%15 = OpTypeSampledImage %14
+%S_t = OpTypeStruct %v2float %v2float %15
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%17 = OpTypeFunction %void %_ptr_Function_S_t
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%_ptr_Function_15 = OpTypePointer Function %15
+%sampler15 = OpVariable %_ptr_UniformConstant_15 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%25 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%26 = OpLoad %v2float %texCoords
+%27 = OpLoad %S_t %s0
+%28 = OpCompositeInsert %S_t %26 %27 0
+%29 = OpLoad %15 %sampler15
+%30 = OpCompositeInsert %S_t %29 %28 2
+OpStore %s0 %30
+%31 = OpCompositeExtract %15 %30 2
+%32 = OpCompositeExtract %v2float %30 0
+%33 = OpImageSampleImplicitLod %v4float %31 %32
+OpStore %outColor %33
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%25 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%26 = OpLoad %v2float %texCoords
+%27 = OpLoad %S_t %s0
+%28 = OpCompositeInsert %S_t %26 %27 0
+%29 = OpLoad %15 %sampler15
+%30 = OpCompositeInsert %S_t %29 %28 2
+OpStore %s0 %30
+%33 = OpImageSampleImplicitLod %v4float %29 %26
+OpStore %outColor %33
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(predefs + before, predefs + after,
+ true, true);
+}
+
+TEST_F(InsertExtractElimTest, OptimizeNestedStruct) {
+ // The following HLSL has been pre-optimized to get the SPIR-V:
+ // struct S0
+ // {
+ // int x;
+ // SamplerState ss;
+ // };
+ //
+ // struct S1
+ // {
+ // float b;
+ // S0 s0;
+ // };
+ //
+ // struct S2
+ // {
+ // int a1;
+ // S1 resources;
+ // };
+ //
+ // SamplerState samp;
+ // Texture2D tex;
+ //
+ // float4 main(float4 vpos : VPOS) : COLOR0
+ // {
+ // S1 s1;
+ // S2 s2;
+ // s1.s0.ss = samp;
+ // s2.resources = s1;
+ // return tex.Sample(s2.resources.s0.ss, float2(0.5));
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %_entryPointOutput
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 500
+OpName %main "main"
+OpName %S0 "S0"
+OpMemberName %S0 0 "x"
+OpMemberName %S0 1 "ss"
+OpName %S1 "S1"
+OpMemberName %S1 0 "b"
+OpMemberName %S1 1 "s0"
+OpName %samp "samp"
+OpName %S2 "S2"
+OpMemberName %S2 0 "a1"
+OpMemberName %S2 1 "resources"
+OpName %tex "tex"
+OpName %_entryPointOutput "@entryPointOutput"
+OpDecorate %samp DescriptorSet 0
+OpDecorate %tex DescriptorSet 0
+OpDecorate %_entryPointOutput Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %v4float %_ptr_Function_v4float
+%int = OpTypeInt 32 1
+%16 = OpTypeSampler
+%S0 = OpTypeStruct %int %16
+%S1 = OpTypeStruct %float %S0
+%_ptr_Function_S1 = OpTypePointer Function %S1
+%int_1 = OpConstant %int 1
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%samp = OpVariable %_ptr_UniformConstant_16 UniformConstant
+%_ptr_Function_16 = OpTypePointer Function %16
+%S2 = OpTypeStruct %int %S1
+%_ptr_Function_S2 = OpTypePointer Function %S2
+%22 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_22 = OpTypePointer UniformConstant %22
+%tex = OpVariable %_ptr_UniformConstant_22 UniformConstant
+%24 = OpTypeSampledImage %22
+%v2float = OpTypeVector %float 2
+%float_0_5 = OpConstant %float 0.5
+%27 = OpConstantComposite %v2float %float_0_5 %float_0_5
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpVariable %_ptr_Function_S1 Function
+%32 = OpVariable %_ptr_Function_S2 Function
+%33 = OpLoad %16 %samp
+%34 = OpLoad %S1 %31
+%35 = OpCompositeInsert %S1 %33 %34 1 1
+OpStore %31 %35
+%36 = OpLoad %S2 %32
+%37 = OpCompositeInsert %S2 %35 %36 1
+OpStore %32 %37
+%38 = OpLoad %22 %tex
+%39 = OpCompositeExtract %16 %37 1 1 1
+%40 = OpSampledImage %24 %38 %39
+%41 = OpImageSampleImplicitLod %v4float %40 %27
+OpStore %_entryPointOutput %41
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpVariable %_ptr_Function_S1 Function
+%32 = OpVariable %_ptr_Function_S2 Function
+%33 = OpLoad %16 %samp
+%34 = OpLoad %S1 %31
+%35 = OpCompositeInsert %S1 %33 %34 1 1
+OpStore %31 %35
+%36 = OpLoad %S2 %32
+%37 = OpCompositeInsert %S2 %35 %36 1
+OpStore %32 %37
+%38 = OpLoad %22 %tex
+%40 = OpSampledImage %24 %38 %33
+%41 = OpImageSampleImplicitLod %v4float %40 %27
+OpStore %_entryPointOutput %41
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(predefs + before, predefs + after,
+ true, true);
+}
+
+TEST_F(InsertExtractElimTest, ConflictingInsertPreventsOptimization) {
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // s0.v1[2] = 0.0;
+ // gl_FragColor = s0.v1;
+ // }
+
+ const std::string assembly =
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%float_0 = OpConstant %float 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %8
+%18 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%19 = OpLoad %v4float %BaseColor
+%20 = OpLoad %S_t %s0
+%21 = OpCompositeInsert %S_t %19 %20 1
+%22 = OpCompositeInsert %S_t %float_0 %21 1 2
+OpStore %s0 %22
+%23 = OpCompositeExtract %v4float %22 1
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(assembly, assembly, true, true);
+}
+
+TEST_F(InsertExtractElimTest, ConflictingInsertPreventsOptimization2) {
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1[1] = 1.0; // dead
+ // s0.v1 = Baseline;
+ // gl_FragColor = vec4(s0.v1[1], 0.0, 0.0, 0.0);
+ // }
+
+ const std::string before_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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%float_1 = OpConstant %float 1
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+)";
+
+ const std::string after_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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%float_1 = OpConstant %float 1
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%22 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%23 = OpLoad %S_t %s0
+%24 = OpCompositeInsert %S_t %float_1 %23 1 1
+%25 = OpLoad %v4float %BaseColor
+%26 = OpCompositeInsert %S_t %25 %24 1
+%27 = OpCompositeExtract %float %26 1 1
+%28 = OpCompositeConstruct %v4float %27 %float_0 %float_0 %float_0
+OpStore %gl_FragColor %28
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%22 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%23 = OpLoad %S_t %s0
+%24 = OpCompositeInsert %S_t %float_1 %23 1 1
+%25 = OpLoad %v4float %BaseColor
+%26 = OpCompositeInsert %S_t %25 %24 1
+%27 = OpCompositeExtract %float %25 1
+%28 = OpCompositeConstruct %v4float %27 %float_0 %float_0 %float_0
+OpStore %gl_FragColor %28
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(before_predefs + before,
+ after_predefs + after, true, true);
+}
+
+TEST_F(InsertExtractElimTest, MixWithConstants) {
+ // Extract component of FMix with 0.0 or 1.0 as the a-value.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in float bc;
+ // layout (location=1) in float bc2;
+ // layout (location=2) in float m;
+ // layout (location=3) in float m2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec4 bcv = vec4(bc, bc2, 0.0, 1.0);
+ // vec4 bcv2 = vec4(bc2, bc, 1.0, 0.0);
+ // vec4 v = mix(bcv, bcv2, vec4(0.0,1.0,m,m2));
+ // OutColor = vec4(v.y);
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %bc %bc2 %m %m2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %bc "bc"
+OpName %bc2 "bc2"
+OpName %m "m"
+OpName %m2 "m2"
+OpName %OutColor "OutColor"
+OpDecorate %bc Location 0
+OpDecorate %bc2 Location 1
+OpDecorate %m Location 2
+OpDecorate %m2 Location 3
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_float = OpTypePointer Input %float
+%bc = OpVariable %_ptr_Input_float Input
+%bc2 = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%m = OpVariable %_ptr_Input_float Input
+%m2 = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%20 = OpLoad %float %bc
+%21 = OpLoad %float %bc2
+%22 = OpCompositeConstruct %v4float %20 %21 %float_0 %float_1
+%23 = OpLoad %float %bc2
+%24 = OpLoad %float %bc
+%25 = OpCompositeConstruct %v4float %23 %24 %float_1 %float_0
+%26 = OpLoad %float %m
+%27 = OpLoad %float %m2
+%28 = OpCompositeConstruct %v4float %float_0 %float_1 %26 %27
+%29 = OpExtInst %v4float %1 FMix %22 %25 %28
+%30 = OpCompositeExtract %float %29 1
+%31 = OpCompositeConstruct %v4float %30 %30 %30 %30
+OpStore %OutColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%20 = OpLoad %float %bc
+%21 = OpLoad %float %bc2
+%22 = OpCompositeConstruct %v4float %20 %21 %float_0 %float_1
+%23 = OpLoad %float %bc2
+%24 = OpLoad %float %bc
+%25 = OpCompositeConstruct %v4float %23 %24 %float_1 %float_0
+%26 = OpLoad %float %m
+%27 = OpLoad %float %m2
+%28 = OpCompositeConstruct %v4float %float_0 %float_1 %26 %27
+%29 = OpExtInst %v4float %1 FMix %22 %25 %28
+%31 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %OutColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(predefs + before, predefs + after,
+ true, true);
+}
+
+TEST_F(InsertExtractElimTest, VectorShuffle1) {
+ // Extract component from first vector in VectorShuffle
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in float bc;
+ // layout (location=1) in float bc2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec4 bcv = vec4(bc, bc2, 0.0, 1.0);
+ // vec4 v = bcv.zwxy;
+ // OutColor = vec4(v.y);
+ // }
+
+ const std::string predefs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %bc %bc2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %bc "bc"
+OpName %bc2 "bc2"
+OpName %OutColor "OutColor"
+OpDecorate %bc Location 0
+OpDecorate %bc2 Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_float = OpTypePointer Input %float
+%bc = OpVariable %_ptr_Input_float Input
+%bc2 = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+)";
+
+ const std::string predefs_after = predefs_before +
+ "%24 = OpConstantComposite %v4float "
+ "%float_1 %float_1 %float_1 %float_1\n";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%17 = OpLabel
+%18 = OpLoad %float %bc
+%19 = OpLoad %float %bc2
+%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1
+%21 = OpVectorShuffle %v4float %20 %20 2 3 0 1
+%22 = OpCompositeExtract %float %21 1
+%23 = OpCompositeConstruct %v4float %22 %22 %22 %22
+OpStore %OutColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%17 = OpLabel
+%18 = OpLoad %float %bc
+%19 = OpLoad %float %bc2
+%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1
+%21 = OpVectorShuffle %v4float %20 %20 2 3 0 1
+OpStore %OutColor %24
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(predefs_before + before,
+ predefs_after + after, true, true);
+}
+
+TEST_F(InsertExtractElimTest, VectorShuffle2) {
+ // Extract component from second vector in VectorShuffle
+ // Identical to test VectorShuffle1 except for the vector
+ // shuffle index of 7.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in float bc;
+ // layout (location=1) in float bc2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec4 bcv = vec4(bc, bc2, 0.0, 1.0);
+ // vec4 v = bcv.zwxy;
+ // OutColor = vec4(v.y);
+ // }
+
+ const std::string predefs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %bc %bc2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %bc "bc"
+OpName %bc2 "bc2"
+OpName %OutColor "OutColor"
+OpDecorate %bc Location 0
+OpDecorate %bc2 Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_float = OpTypePointer Input %float
+%bc = OpVariable %_ptr_Input_float Input
+%bc2 = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+)";
+
+ const std::string predefs_after =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %bc %bc2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %bc "bc"
+OpName %bc2 "bc2"
+OpName %OutColor "OutColor"
+OpDecorate %bc Location 0
+OpDecorate %bc2 Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%7 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_float = OpTypePointer Input %float
+%bc = OpVariable %_ptr_Input_float Input
+%bc2 = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%24 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%17 = OpLabel
+%18 = OpLoad %float %bc
+%19 = OpLoad %float %bc2
+%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1
+%21 = OpVectorShuffle %v4float %20 %20 2 7 0 1
+%22 = OpCompositeExtract %float %21 1
+%23 = OpCompositeConstruct %v4float %22 %22 %22 %22
+OpStore %OutColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%17 = OpLabel
+%18 = OpLoad %float %bc
+%19 = OpLoad %float %bc2
+%20 = OpCompositeConstruct %v4float %18 %19 %float_0 %float_1
+%21 = OpVectorShuffle %v4float %20 %20 2 7 0 1
+OpStore %OutColor %24
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<SimplificationPass>(predefs_before + before,
+ predefs_after + after, true, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/inst_bindless_check_test.cpp b/test/opt/inst_bindless_check_test.cpp
new file mode 100644
index 0000000..a426ce0
--- /dev/null
+++ b/test/opt/inst_bindless_check_test.cpp
@@ -0,0 +1,1857 @@
+// 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 <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using InstBindlessTest = PassTest<::testing::Test>;
+
+TEST_F(InstBindlessTest, Simple) {
+ // Texture2D g_tColor[128];
+ //
+ // layout(push_constant) cbuffer PerViewConstantBuffer_t
+ // {
+ // uint g_nDataIdx;
+ // };
+ //
+ // SamplerState g_sAniso;
+ //
+ // struct PS_INPUT
+ // {
+ // float2 vTextureCoords : TEXCOORD2;
+ // };
+ //
+ // struct PS_OUTPUT
+ // {
+ // float4 vColor : SV_Target0;
+ // };
+ //
+ // PS_OUTPUT MainPs(PS_INPUT i)
+ // {
+ // PS_OUTPUT ps_output;
+ // ps_output.vColor =
+ // g_tColor[ g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+ // return ps_output;
+ // }
+
+ const std::string entry_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+)";
+
+ const std::string entry_after =
+ R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+)";
+
+ const std::string names_annots =
+ R"(OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+)";
+
+ const std::string new_annots =
+ R"(OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_55 Block
+OpMemberDecorate %_struct_55 0 Offset 0
+OpMemberDecorate %_struct_55 1 Offset 4
+OpDecorate %57 DescriptorSet 7
+OpDecorate %57 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+)";
+
+ const std::string consts_types_vars =
+ R"(%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_16_uint_128 = OpTypeArray %16 %uint_128
+%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_16_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%24 = OpTypeSampler
+%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
+%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
+%26 = OpTypeSampledImage %16
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string new_consts_types_vars =
+ R"(%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%48 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_55 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_55 = OpTypePointer StorageBuffer %_struct_55
+%57 = OpVariable %_ptr_StorageBuffer__struct_55 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_56 = OpConstant %uint 56
+%103 = OpConstantNull %v4float
+)";
+
+ const std::string func_pt1 =
+ R"(%MainPs = OpFunction %void None %10
+%29 = OpLabel
+%30 = OpLoad %v2float %i_vTextureCoords
+%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%32 = OpLoad %uint %31
+%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
+%34 = OpLoad %16 %33
+%35 = OpLoad %24 %g_sAniso
+%36 = OpSampledImage %26 %34 %35
+)";
+
+ const std::string func_pt2_before =
+ R"(%37 = OpImageSampleImplicitLod %v4float %36 %30
+OpStore %_entryPointOutput_vColor %37
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_pt2_after =
+ R"(%40 = OpULessThan %bool %32 %uint_128
+OpSelectionMerge %41 None
+OpBranchConditional %40 %42 %43
+%42 = OpLabel
+%44 = OpLoad %16 %33
+%45 = OpSampledImage %26 %44 %35
+%46 = OpImageSampleImplicitLod %v4float %45 %30
+OpBranch %41
+%43 = OpLabel
+%102 = OpFunctionCall %void %47 %uint_56 %uint_0 %32 %uint_128
+OpBranch %41
+%41 = OpLabel
+%104 = OpPhi %v4float %46 %42 %103 %43
+OpStore %_entryPointOutput_vColor %104
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%47 = OpFunction %void None %48
+%49 = OpFunctionParameter %uint
+%50 = OpFunctionParameter %uint
+%51 = OpFunctionParameter %uint
+%52 = OpFunctionParameter %uint
+%53 = OpLabel
+%59 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_0
+%62 = OpAtomicIAdd %uint %59 %uint_4 %uint_0 %uint_9
+%63 = OpIAdd %uint %62 %uint_9
+%64 = OpArrayLength %uint %57 1
+%65 = OpULessThanEqual %bool %63 %64
+OpSelectionMerge %66 None
+OpBranchConditional %65 %67 %66
+%67 = OpLabel
+%68 = OpIAdd %uint %62 %uint_0
+%70 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %68
+OpStore %70 %uint_9
+%72 = OpIAdd %uint %62 %uint_1
+%73 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %72
+OpStore %73 %uint_23
+%75 = OpIAdd %uint %62 %uint_2
+%76 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %75
+OpStore %76 %49
+%78 = OpIAdd %uint %62 %uint_3
+%79 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %78
+OpStore %79 %uint_4
+%82 = OpLoad %v4float %gl_FragCoord
+%84 = OpBitcast %v4uint %82
+%85 = OpCompositeExtract %uint %84 0
+%86 = OpIAdd %uint %62 %uint_4
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %86
+OpStore %87 %85
+%88 = OpCompositeExtract %uint %84 1
+%90 = OpIAdd %uint %62 %uint_5
+%91 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %90
+OpStore %91 %88
+%93 = OpIAdd %uint %62 %uint_6
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %93
+OpStore %94 %50
+%96 = OpIAdd %uint %62 %uint_7
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %96
+OpStore %97 %51
+%99 = OpIAdd %uint %62 %uint_8
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %57 %uint_1 %99
+OpStore %100 %52
+OpBranch %66
+%66 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ entry_before + names_annots + consts_types_vars + func_pt1 +
+ func_pt2_before,
+ entry_after + names_annots + new_annots + consts_types_vars +
+ new_consts_types_vars + func_pt1 + func_pt2_after + output_func,
+ true, true);
+}
+
+TEST_F(InstBindlessTest, NoInstrumentConstIndexInbounds) {
+ // Texture2D g_tColor[128];
+ //
+ // SamplerState g_sAniso;
+ //
+ // struct PS_INPUT
+ // {
+ // float2 vTextureCoords : TEXCOORD2;
+ // };
+ //
+ // struct PS_OUTPUT
+ // {
+ // float4 vColor : SV_Target0;
+ // };
+ //
+ // PS_OUTPUT MainPs(PS_INPUT i)
+ // {
+ // PS_OUTPUT ps_output;
+ //
+ // ps_output.vColor = g_tColor[ 37 ].Sample(g_sAniso, i.vTextureCoords.xy);
+ // return ps_output;
+ // }
+
+ const std::string before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_37 = OpConstant %int 37
+%15 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_15_uint_128 = OpTypeArray %15 %uint_128
+%_ptr_UniformConstant__arr_15_uint_128 = OpTypePointer UniformConstant %_arr_15_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_15_uint_128 UniformConstant
+%_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
+%21 = OpTypeSampler
+%_ptr_UniformConstant_21 = OpTypePointer UniformConstant %21
+%g_sAniso = OpVariable %_ptr_UniformConstant_21 UniformConstant
+%23 = OpTypeSampledImage %15
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%MainPs = OpFunction %void None %8
+%26 = OpLabel
+%27 = OpLoad %v2float %i_vTextureCoords
+%28 = OpAccessChain %_ptr_UniformConstant_15 %g_tColor %int_37
+%29 = OpLoad %15 %28
+%30 = OpLoad %21 %g_sAniso
+%31 = OpSampledImage %23 %29 %30
+%32 = OpImageSampleImplicitLod %v4float %31 %27
+OpStore %_entryPointOutput_vColor %32
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(before, before, true, true);
+}
+
+TEST_F(InstBindlessTest, InstrumentMultipleInstructions) {
+ // Texture2D g_tColor[128];
+ //
+ // layout(push_constant) cbuffer PerViewConstantBuffer_t
+ // {
+ // uint g_nDataIdx;
+ // uint g_nDataIdx2;
+ // };
+ //
+ // SamplerState g_sAniso;
+ //
+ // struct PS_INPUT
+ // {
+ // float2 vTextureCoords : TEXCOORD2;
+ // };
+ //
+ // struct PS_OUTPUT
+ // {
+ // float4 vColor : SV_Target0;
+ // };
+ //
+ // PS_OUTPUT MainPs(PS_INPUT i)
+ // {
+ // PS_OUTPUT ps_output;
+ //
+ // float t = g_tColor[g_nDataIdx ].Sample(g_sAniso, i.vTextureCoords.xy);
+ // float t2 = g_tColor[g_nDataIdx2].Sample(g_sAniso, i.vTextureCoords.xy);
+ // ps_output.vColor = t + t2;
+ // return ps_output;
+ // }
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_17_uint_128 = OpTypeArray %17 %uint_128
+%_ptr_UniformConstant__arr_17_uint_128 = OpTypePointer UniformConstant %_arr_17_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_17_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
+%25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
+%27 = OpTypeSampledImage %17
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_63 Block
+OpMemberDecorate %_struct_63 0 Offset 0
+OpMemberDecorate %_struct_63 1 Offset 4
+OpDecorate %65 DescriptorSet 7
+OpDecorate %65 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_17_uint_128 = OpTypeArray %17 %uint_128
+%_ptr_UniformConstant__arr_17_uint_128 = OpTypePointer UniformConstant %_arr_17_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_17_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_17 = OpTypePointer UniformConstant %17
+%25 = OpTypeSampler
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%g_sAniso = OpVariable %_ptr_UniformConstant_25 UniformConstant
+%27 = OpTypeSampledImage %17
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%56 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_63 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_63 = OpTypePointer StorageBuffer %_struct_63
+%65 = OpVariable %_ptr_StorageBuffer__struct_63 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_58 = OpConstant %uint 58
+%111 = OpConstantNull %v4float
+%uint_64 = OpConstant %uint 64
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpLoad %v2float %i_vTextureCoords
+%32 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%33 = OpLoad %uint %32
+%34 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %33
+%35 = OpLoad %17 %34
+%36 = OpLoad %25 %g_sAniso
+%37 = OpSampledImage %27 %35 %36
+%38 = OpImageSampleImplicitLod %v4float %37 %31
+%39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%40 = OpLoad %uint %39
+%41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
+%42 = OpLoad %17 %41
+%43 = OpSampledImage %27 %42 %36
+%44 = OpImageSampleImplicitLod %v4float %43 %31
+%45 = OpFAdd %v4float %38 %44
+OpStore %_entryPointOutput_vColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %10
+%30 = OpLabel
+%31 = OpLoad %v2float %i_vTextureCoords
+%32 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%33 = OpLoad %uint %32
+%34 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %33
+%35 = OpLoad %17 %34
+%36 = OpLoad %25 %g_sAniso
+%37 = OpSampledImage %27 %35 %36
+%48 = OpULessThan %bool %33 %uint_128
+OpSelectionMerge %49 None
+OpBranchConditional %48 %50 %51
+%50 = OpLabel
+%52 = OpLoad %17 %34
+%53 = OpSampledImage %27 %52 %36
+%54 = OpImageSampleImplicitLod %v4float %53 %31
+OpBranch %49
+%51 = OpLabel
+%110 = OpFunctionCall %void %55 %uint_58 %uint_0 %33 %uint_128
+OpBranch %49
+%49 = OpLabel
+%112 = OpPhi %v4float %54 %50 %111 %51
+%39 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%40 = OpLoad %uint %39
+%41 = OpAccessChain %_ptr_UniformConstant_17 %g_tColor %40
+%42 = OpLoad %17 %41
+%43 = OpSampledImage %27 %42 %36
+%113 = OpULessThan %bool %40 %uint_128
+OpSelectionMerge %114 None
+OpBranchConditional %113 %115 %116
+%115 = OpLabel
+%117 = OpLoad %17 %41
+%118 = OpSampledImage %27 %117 %36
+%119 = OpImageSampleImplicitLod %v4float %118 %31
+OpBranch %114
+%116 = OpLabel
+%121 = OpFunctionCall %void %55 %uint_64 %uint_0 %40 %uint_128
+OpBranch %114
+%114 = OpLabel
+%122 = OpPhi %v4float %119 %115 %111 %116
+%45 = OpFAdd %v4float %112 %122
+OpStore %_entryPointOutput_vColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%55 = OpFunction %void None %56
+%57 = OpFunctionParameter %uint
+%58 = OpFunctionParameter %uint
+%59 = OpFunctionParameter %uint
+%60 = OpFunctionParameter %uint
+%61 = OpLabel
+%67 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_0
+%70 = OpAtomicIAdd %uint %67 %uint_4 %uint_0 %uint_9
+%71 = OpIAdd %uint %70 %uint_9
+%72 = OpArrayLength %uint %65 1
+%73 = OpULessThanEqual %bool %71 %72
+OpSelectionMerge %74 None
+OpBranchConditional %73 %75 %74
+%75 = OpLabel
+%76 = OpIAdd %uint %70 %uint_0
+%78 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %76
+OpStore %78 %uint_9
+%80 = OpIAdd %uint %70 %uint_1
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %80
+OpStore %81 %uint_23
+%83 = OpIAdd %uint %70 %uint_2
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %83
+OpStore %84 %57
+%86 = OpIAdd %uint %70 %uint_3
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %86
+OpStore %87 %uint_4
+%90 = OpLoad %v4float %gl_FragCoord
+%92 = OpBitcast %v4uint %90
+%93 = OpCompositeExtract %uint %92 0
+%94 = OpIAdd %uint %70 %uint_4
+%95 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %94
+OpStore %95 %93
+%96 = OpCompositeExtract %uint %92 1
+%98 = OpIAdd %uint %70 %uint_5
+%99 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %98
+OpStore %99 %96
+%101 = OpIAdd %uint %70 %uint_6
+%102 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %101
+OpStore %102 %58
+%104 = OpIAdd %uint %70 %uint_7
+%105 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %104
+OpStore %105 %59
+%107 = OpIAdd %uint %70 %uint_8
+%108 = OpAccessChain %_ptr_StorageBuffer_uint %65 %uint_1 %107
+OpStore %108 %60
+OpBranch %74
+%74 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ defs_before + func_before, defs_after + func_after + output_func, true,
+ true);
+}
+
+TEST_F(InstBindlessTest, ReuseConstsTypesBuiltins) {
+ // This test verifies that the pass resuses existing constants, types
+ // and builtin variables. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %85 DescriptorSet 7
+OpDecorate %85 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_20_uint_128 = OpTypeArray %20 %uint_128
+%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%35 = OpTypeSampler
+%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
+%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
+%39 = OpTypeSampledImage %20
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_83 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_83 = OpTypePointer StorageBuffer %_struct_83
+%85 = OpVariable %_ptr_StorageBuffer__struct_83 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_10 = OpConstant %uint 10
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_9 = OpConstant %uint 9
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%131 = OpConstantNull %v4float
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %g_sAniso "g_sAniso"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %10 DescriptorSet 7
+OpDecorate %10 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_34 Block
+OpMemberDecorate %_struct_34 0 Offset 0
+OpMemberDecorate %_struct_34 1 Offset 4
+OpDecorate %74 DescriptorSet 7
+OpDecorate %74 Binding 0
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%26 = OpTypeSampler
+%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26
+%g_sAniso = OpVariable %_ptr_UniformConstant_26 UniformConstant
+%28 = OpTypeSampledImage %18
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_34 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_34 = OpTypePointer StorageBuffer %_struct_34
+%10 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_10 = OpConstant %uint 10
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_9 = OpConstant %uint 9
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%50 = OpConstantNull %v4float
+%68 = OpTypeFunction %void %uint %uint %uint %uint
+%74 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
+%uint_82 = OpConstant %uint 82
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%67 = OpLoad %35 %g_sAniso
+%78 = OpLoad %20 %65
+%79 = OpSampledImage %39 %78 %67
+%71 = OpImageSampleImplicitLod %v4float %79 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %12
+%51 = OpLabel
+%52 = OpLoad %v2float %i_vTextureCoords
+%53 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%54 = OpLoad %uint %53
+%55 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %54
+%56 = OpLoad %26 %g_sAniso
+%57 = OpLoad %18 %55
+%58 = OpSampledImage %28 %57 %56
+%60 = OpULessThan %bool %54 %uint_128
+OpSelectionMerge %61 None
+OpBranchConditional %60 %62 %63
+%62 = OpLabel
+%64 = OpLoad %18 %55
+%65 = OpSampledImage %28 %64 %56
+%66 = OpImageSampleImplicitLod %v4float %65 %52
+OpBranch %61
+%63 = OpLabel
+%105 = OpFunctionCall %void %67 %uint_82 %uint_0 %54 %uint_128
+OpBranch %61
+%61 = OpLabel
+%106 = OpPhi %v4float %66 %62 %50 %63
+OpStore %_entryPointOutput_vColor %106
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%67 = OpFunction %void None %68
+%69 = OpFunctionParameter %uint
+%70 = OpFunctionParameter %uint
+%71 = OpFunctionParameter %uint
+%72 = OpFunctionParameter %uint
+%73 = OpLabel
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_0
+%76 = OpAtomicIAdd %uint %75 %uint_4 %uint_0 %uint_9
+%77 = OpIAdd %uint %76 %uint_9
+%78 = OpArrayLength %uint %74 1
+%79 = OpULessThanEqual %bool %77 %78
+OpSelectionMerge %80 None
+OpBranchConditional %79 %81 %80
+%81 = OpLabel
+%82 = OpIAdd %uint %76 %uint_0
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %82
+OpStore %83 %uint_9
+%84 = OpIAdd %uint %76 %uint_1
+%85 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %84
+OpStore %85 %uint_23
+%86 = OpIAdd %uint %76 %uint_2
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %86
+OpStore %87 %69
+%88 = OpIAdd %uint %76 %uint_3
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %88
+OpStore %89 %uint_4
+%90 = OpLoad %v4float %gl_FragCoord
+%91 = OpBitcast %v4uint %90
+%92 = OpCompositeExtract %uint %91 0
+%93 = OpIAdd %uint %76 %uint_4
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %93
+OpStore %94 %92
+%95 = OpCompositeExtract %uint %91 1
+%96 = OpIAdd %uint %76 %uint_5
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %96
+OpStore %97 %95
+%98 = OpIAdd %uint %76 %uint_6
+%99 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %98
+OpStore %99 %70
+%100 = OpIAdd %uint %76 %uint_7
+%101 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %100
+OpStore %101 %71
+%102 = OpIAdd %uint %76 %uint_8
+%103 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %102
+OpStore %103 %72
+OpBranch %80
+%80 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ defs_before + func_before, defs_after + func_after + output_func, true,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentOpImage) {
+ // This test verifies that the pass will correctly instrument shader
+ // using OpImage. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+OpCapability StorageImageReadWithoutFormat
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_39 = OpTypePointer UniformConstant %39
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpCapability StorageImageReadWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_51 Block
+OpMemberDecorate %_struct_51 0 Offset 0
+OpMemberDecorate %_struct_51 1 Offset 4
+OpDecorate %53 DescriptorSet 7
+OpDecorate %53 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%15 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%18 = OpTypeSampledImage %15
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%44 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_51 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_51 = OpTypePointer StorageBuffer %_struct_51
+%53 = OpVariable %_ptr_StorageBuffer__struct_51 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_51 = OpConstant %uint 51
+%99 = OpConstantNull %v4float
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%75 = OpImage %20 %66
+%71 = OpImageRead %v4float %75 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %9
+%26 = OpLabel
+%27 = OpLoad %v2int %i_vTextureCoords
+%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
+%31 = OpLoad %18 %30
+%32 = OpImage %15 %31
+%36 = OpULessThan %bool %29 %uint_128
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %39
+%38 = OpLabel
+%40 = OpLoad %18 %30
+%41 = OpImage %15 %40
+%42 = OpImageRead %v4float %41 %27
+OpBranch %37
+%39 = OpLabel
+%98 = OpFunctionCall %void %43 %uint_51 %uint_0 %29 %uint_128
+OpBranch %37
+%37 = OpLabel
+%100 = OpPhi %v4float %42 %38 %99 %39
+OpStore %_entryPointOutput_vColor %100
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%43 = OpFunction %void None %44
+%45 = OpFunctionParameter %uint
+%46 = OpFunctionParameter %uint
+%47 = OpFunctionParameter %uint
+%48 = OpFunctionParameter %uint
+%49 = OpLabel
+%55 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_0
+%58 = OpAtomicIAdd %uint %55 %uint_4 %uint_0 %uint_9
+%59 = OpIAdd %uint %58 %uint_9
+%60 = OpArrayLength %uint %53 1
+%61 = OpULessThanEqual %bool %59 %60
+OpSelectionMerge %62 None
+OpBranchConditional %61 %63 %62
+%63 = OpLabel
+%64 = OpIAdd %uint %58 %uint_0
+%66 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %64
+OpStore %66 %uint_9
+%68 = OpIAdd %uint %58 %uint_1
+%69 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %68
+OpStore %69 %uint_23
+%71 = OpIAdd %uint %58 %uint_2
+%72 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %71
+OpStore %72 %45
+%74 = OpIAdd %uint %58 %uint_3
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %74
+OpStore %75 %uint_4
+%78 = OpLoad %v4float %gl_FragCoord
+%80 = OpBitcast %v4uint %78
+%81 = OpCompositeExtract %uint %80 0
+%82 = OpIAdd %uint %58 %uint_4
+%83 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %82
+OpStore %83 %81
+%84 = OpCompositeExtract %uint %80 1
+%86 = OpIAdd %uint %58 %uint_5
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %86
+OpStore %87 %84
+%89 = OpIAdd %uint %58 %uint_6
+%90 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %89
+OpStore %90 %46
+%92 = OpIAdd %uint %58 %uint_7
+%93 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %92
+OpStore %93 %47
+%95 = OpIAdd %uint %58 %uint_8
+%96 = OpAccessChain %_ptr_StorageBuffer_uint %53 %uint_1 %95
+OpStore %96 %48
+OpBranch %62
+%62 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ defs_before + func_before, defs_after + func_after + output_func, true,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentSampledImage) {
+ // This test verifies that the pass will correctly instrument shader
+ // using sampled image. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%39 = OpTypeSampledImage %20
+%_arr_39_uint_128 = OpTypeArray %39 %uint_128
+%_ptr_UniformConstant__arr_39_uint_128 = OpTypePointer UniformConstant %_arr_39_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_39_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_39 = OpTypePointer UniformConstant %39
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_49 Block
+OpMemberDecorate %_struct_49 0 Offset 0
+OpMemberDecorate %_struct_49 1 Offset 4
+OpDecorate %51 DescriptorSet 7
+OpDecorate %51 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%15 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%18 = OpTypeSampledImage %15
+%_arr_18_uint_128 = OpTypeArray %18 %uint_128
+%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%42 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_49 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_49 = OpTypePointer StorageBuffer %_struct_49
+%51 = OpVariable %_ptr_StorageBuffer__struct_49 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_49 = OpConstant %uint 49
+%97 = OpConstantNull %v4float
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2float %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_39 %g_tColor %64
+%66 = OpLoad %39 %65
+%71 = OpImageSampleImplicitLod %v4float %66 %53
+OpStore %_entryPointOutput_vColor %71
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %9
+%26 = OpLabel
+%27 = OpLoad %v2float %i_vTextureCoords
+%28 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%29 = OpLoad %uint %28
+%30 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %29
+%31 = OpLoad %18 %30
+%35 = OpULessThan %bool %29 %uint_128
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %38
+%37 = OpLabel
+%39 = OpLoad %18 %30
+%40 = OpImageSampleImplicitLod %v4float %39 %27
+OpBranch %36
+%38 = OpLabel
+%96 = OpFunctionCall %void %41 %uint_49 %uint_0 %29 %uint_128
+OpBranch %36
+%36 = OpLabel
+%98 = OpPhi %v4float %40 %37 %97 %38
+OpStore %_entryPointOutput_vColor %98
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%41 = OpFunction %void None %42
+%43 = OpFunctionParameter %uint
+%44 = OpFunctionParameter %uint
+%45 = OpFunctionParameter %uint
+%46 = OpFunctionParameter %uint
+%47 = OpLabel
+%53 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_0
+%56 = OpAtomicIAdd %uint %53 %uint_4 %uint_0 %uint_9
+%57 = OpIAdd %uint %56 %uint_9
+%58 = OpArrayLength %uint %51 1
+%59 = OpULessThanEqual %bool %57 %58
+OpSelectionMerge %60 None
+OpBranchConditional %59 %61 %60
+%61 = OpLabel
+%62 = OpIAdd %uint %56 %uint_0
+%64 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %62
+OpStore %64 %uint_9
+%66 = OpIAdd %uint %56 %uint_1
+%67 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %66
+OpStore %67 %uint_23
+%69 = OpIAdd %uint %56 %uint_2
+%70 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %69
+OpStore %70 %43
+%72 = OpIAdd %uint %56 %uint_3
+%73 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %72
+OpStore %73 %uint_4
+%76 = OpLoad %v4float %gl_FragCoord
+%78 = OpBitcast %v4uint %76
+%79 = OpCompositeExtract %uint %78 0
+%80 = OpIAdd %uint %56 %uint_4
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %80
+OpStore %81 %79
+%82 = OpCompositeExtract %uint %78 1
+%84 = OpIAdd %uint %56 %uint_5
+%85 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %84
+OpStore %85 %82
+%87 = OpIAdd %uint %56 %uint_6
+%88 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %87
+OpStore %88 %44
+%90 = OpIAdd %uint %56 %uint_7
+%91 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %90
+OpStore %91 %45
+%93 = OpIAdd %uint %56 %uint_8
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %51 %uint_1 %93
+OpStore %94 %46
+OpBranch %60
+%60 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ defs_before + func_before, defs_after + func_after + output_func, true,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentImageWrite) {
+ // This test verifies that the pass will correctly instrument shader
+ // doing bindless image write. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+OpCapability StorageImageWriteWithoutFormat
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%20 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%80 = OpConstantNull %v4float
+%_arr_20_uint_128 = OpTypeArray %20 %uint_128
+%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpCapability StorageImageWriteWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
+OpExecutionMode %MainPs OriginUpperLeft
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %g_tColor "g_tColor"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpName %_ ""
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpDecorate %g_tColor DescriptorSet 3
+OpDecorate %g_tColor Binding 0
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_48 Block
+OpMemberDecorate %_struct_48 0 Offset 0
+OpMemberDecorate %_struct_48 1 Offset 4
+OpDecorate %50 DescriptorSet 7
+OpDecorate %50 Binding 0
+OpDecorate %gl_FragCoord BuiltIn FragCoord
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%int = OpTypeInt 32 1
+%v2int = OpTypeVector %int 2
+%int_0 = OpConstant %int 0
+%16 = OpTypeImage %float 2D 0 0 0 0 Unknown
+%uint = OpTypeInt 32 0
+%uint_128 = OpConstant %uint 128
+%19 = OpConstantNull %v4float
+%_arr_16_uint_128 = OpTypeArray %16 %uint_128
+%_ptr_UniformConstant__arr_16_uint_128 = OpTypePointer UniformConstant %_arr_16_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_16_uint_128 UniformConstant
+%PerViewConstantBuffer_t = OpTypeStruct %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
+%_ptr_Input_v2int = OpTypePointer Input %v2int
+%i_vTextureCoords = OpVariable %_ptr_Input_v2int Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%41 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_48 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_48 = OpTypePointer StorageBuffer %_struct_48
+%50 = OpVariable %_ptr_StorageBuffer__struct_48 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_1 = OpConstant %uint 1
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
+%v4uint = OpTypeVector %uint 4
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_51 = OpConstant %uint 51
+)";
+
+ const std::string func_before =
+ R"(%MainPs = OpFunction %void None %3
+%5 = OpLabel
+%53 = OpLoad %v2int %i_vTextureCoords
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%64 = OpLoad %uint %63
+%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
+%66 = OpLoad %20 %65
+OpImageWrite %66 %53 %80
+OpStore %_entryPointOutput_vColor %80
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%MainPs = OpFunction %void None %9
+%27 = OpLabel
+%28 = OpLoad %v2int %i_vTextureCoords
+%29 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%30 = OpLoad %uint %29
+%31 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %30
+%32 = OpLoad %16 %31
+%35 = OpULessThan %bool %30 %uint_128
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %38
+%37 = OpLabel
+%39 = OpLoad %16 %31
+OpImageWrite %39 %28 %19
+OpBranch %36
+%38 = OpLabel
+%95 = OpFunctionCall %void %40 %uint_51 %uint_0 %30 %uint_128
+OpBranch %36
+%36 = OpLabel
+OpStore %_entryPointOutput_vColor %19
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%40 = OpFunction %void None %41
+%42 = OpFunctionParameter %uint
+%43 = OpFunctionParameter %uint
+%44 = OpFunctionParameter %uint
+%45 = OpFunctionParameter %uint
+%46 = OpLabel
+%52 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_0
+%55 = OpAtomicIAdd %uint %52 %uint_4 %uint_0 %uint_9
+%56 = OpIAdd %uint %55 %uint_9
+%57 = OpArrayLength %uint %50 1
+%58 = OpULessThanEqual %bool %56 %57
+OpSelectionMerge %59 None
+OpBranchConditional %58 %60 %59
+%60 = OpLabel
+%61 = OpIAdd %uint %55 %uint_0
+%63 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %61
+OpStore %63 %uint_9
+%65 = OpIAdd %uint %55 %uint_1
+%66 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %65
+OpStore %66 %uint_23
+%68 = OpIAdd %uint %55 %uint_2
+%69 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %68
+OpStore %69 %42
+%71 = OpIAdd %uint %55 %uint_3
+%72 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %71
+OpStore %72 %uint_4
+%75 = OpLoad %v4float %gl_FragCoord
+%77 = OpBitcast %v4uint %75
+%78 = OpCompositeExtract %uint %77 0
+%79 = OpIAdd %uint %55 %uint_4
+%80 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %79
+OpStore %80 %78
+%81 = OpCompositeExtract %uint %77 1
+%83 = OpIAdd %uint %55 %uint_5
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %83
+OpStore %84 %81
+%86 = OpIAdd %uint %55 %uint_6
+%87 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %86
+OpStore %87 %43
+%89 = OpIAdd %uint %55 %uint_7
+%90 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %89
+OpStore %90 %44
+%92 = OpIAdd %uint %55 %uint_8
+%93 = OpAccessChain %_ptr_StorageBuffer_uint %50 %uint_1 %92
+OpStore %93 %45
+OpBranch %59
+%59 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ defs_before + func_before, defs_after + func_after + output_func, true,
+ true);
+}
+
+TEST_F(InstBindlessTest, InstrumentVertexSimple) {
+ // This test verifies that the pass will correctly instrument shader
+ // doing bindless image write. This test was created by editing the SPIR-V
+ // from the Simple test.
+
+ const std::string defs_before =
+ R"(OpCapability Shader
+OpCapability Sampled1D
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %_ %coords2D
+OpSource GLSL 450
+OpName %main "main"
+OpName %lod "lod"
+OpName %coords1D "coords1D"
+OpName %gl_PerVertex "gl_PerVertex"
+OpMemberName %gl_PerVertex 0 "gl_Position"
+OpMemberName %gl_PerVertex 1 "gl_PointSize"
+OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+OpName %_ ""
+OpName %texSampler1D "texSampler1D"
+OpName %foo "foo"
+OpMemberName %foo 0 "g_idx"
+OpName %__0 ""
+OpName %coords2D "coords2D"
+OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+OpDecorate %gl_PerVertex Block
+OpDecorate %texSampler1D DescriptorSet 0
+OpDecorate %texSampler1D Binding 3
+OpMemberDecorate %foo 0 Offset 0
+OpDecorate %foo Block
+OpDecorate %__0 DescriptorSet 0
+OpDecorate %__0 Binding 5
+OpDecorate %coords2D Location 0
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_3 = OpConstant %float 3
+%float_1_78900003 = OpConstant %float 1.78900003
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+%_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%21 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%22 = OpTypeSampledImage %21
+%uint_128 = OpConstant %uint 128
+%_arr_22_uint_128 = OpTypeArray %22 %uint_128
+%_ptr_UniformConstant__arr_22_uint_128 = OpTypePointer UniformConstant %_arr_22_uint_128
+%texSampler1D = OpVariable %_ptr_UniformConstant__arr_22_uint_128 UniformConstant
+%foo = OpTypeStruct %int
+%_ptr_Uniform_foo = OpTypePointer Uniform %foo
+%__0 = OpVariable %_ptr_Uniform_foo Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%_ptr_UniformConstant_22 = OpTypePointer UniformConstant %22
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%coords2D = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string defs_after =
+ R"(OpCapability Shader
+OpCapability Sampled1D
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %_ %coords2D %gl_VertexIndex %gl_InstanceIndex
+OpSource GLSL 450
+OpName %main "main"
+OpName %lod "lod"
+OpName %coords1D "coords1D"
+OpName %gl_PerVertex "gl_PerVertex"
+OpMemberName %gl_PerVertex 0 "gl_Position"
+OpMemberName %gl_PerVertex 1 "gl_PointSize"
+OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+OpMemberName %gl_PerVertex 3 "gl_CullDistance"
+OpName %_ ""
+OpName %texSampler1D "texSampler1D"
+OpName %foo "foo"
+OpMemberName %foo 0 "g_idx"
+OpName %__0 ""
+OpName %coords2D "coords2D"
+OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance
+OpDecorate %gl_PerVertex Block
+OpDecorate %texSampler1D DescriptorSet 0
+OpDecorate %texSampler1D Binding 3
+OpMemberDecorate %foo 0 Offset 0
+OpDecorate %foo Block
+OpDecorate %__0 DescriptorSet 0
+OpDecorate %__0 Binding 5
+OpDecorate %coords2D Location 0
+OpDecorate %_runtimearr_uint ArrayStride 4
+OpDecorate %_struct_61 Block
+OpMemberDecorate %_struct_61 0 Offset 0
+OpMemberDecorate %_struct_61 1 Offset 4
+OpDecorate %63 DescriptorSet 7
+OpDecorate %63 Binding 0
+OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_3 = OpConstant %float 3
+%float_1_78900003 = OpConstant %float 1.78900003
+%v4float = OpTypeVector %float 4
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+%_ = OpVariable %_ptr_Output_gl_PerVertex Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%24 = OpTypeImage %float 1D 0 0 0 1 Unknown
+%25 = OpTypeSampledImage %24
+%uint_128 = OpConstant %uint 128
+%_arr_25_uint_128 = OpTypeArray %25 %uint_128
+%_ptr_UniformConstant__arr_25_uint_128 = OpTypePointer UniformConstant %_arr_25_uint_128
+%texSampler1D = OpVariable %_ptr_UniformConstant__arr_25_uint_128 UniformConstant
+%foo = OpTypeStruct %int
+%_ptr_Uniform_foo = OpTypePointer Uniform %foo
+%__0 = OpVariable %_ptr_Uniform_foo Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%v2float = OpTypeVector %float 2
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%coords2D = OpVariable %_ptr_Input_v2float Input
+%uint_0 = OpConstant %uint 0
+%bool = OpTypeBool
+%54 = OpTypeFunction %void %uint %uint %uint %uint
+%_runtimearr_uint = OpTypeRuntimeArray %uint
+%_struct_61 = OpTypeStruct %uint %_runtimearr_uint
+%_ptr_StorageBuffer__struct_61 = OpTypePointer StorageBuffer %_struct_61
+%63 = OpVariable %_ptr_StorageBuffer__struct_61 StorageBuffer
+%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
+%uint_9 = OpConstant %uint 9
+%uint_4 = OpConstant %uint 4
+%uint_23 = OpConstant %uint 23
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%_ptr_Input_uint = OpTypePointer Input %uint
+%gl_VertexIndex = OpVariable %_ptr_Input_uint Input
+%gl_InstanceIndex = OpVariable %_ptr_Input_uint Input
+%uint_5 = OpConstant %uint 5
+%uint_6 = OpConstant %uint 6
+%uint_7 = OpConstant %uint 7
+%uint_8 = OpConstant %uint 8
+%uint_74 = OpConstant %uint 74
+%106 = OpConstantNull %v4float
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %3
+%5 = OpLabel
+%lod = OpVariable %_ptr_Function_float Function
+%coords1D = OpVariable %_ptr_Function_float Function
+OpStore %lod %float_3
+OpStore %coords1D %float_1_78900003
+%31 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
+%32 = OpLoad %int %31
+%34 = OpAccessChain %_ptr_UniformConstant_22 %texSampler1D %32
+%35 = OpLoad %22 %34
+%36 = OpLoad %float %coords1D
+%37 = OpLoad %float %lod
+%38 = OpImageSampleExplicitLod %v4float %35 %36 Lod %37
+%40 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+OpStore %40 %38
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %12
+%35 = OpLabel
+%lod = OpVariable %_ptr_Function_float Function
+%coords1D = OpVariable %_ptr_Function_float Function
+OpStore %lod %float_3
+OpStore %coords1D %float_1_78900003
+%36 = OpAccessChain %_ptr_Uniform_int %__0 %int_0
+%37 = OpLoad %int %36
+%38 = OpAccessChain %_ptr_UniformConstant_25 %texSampler1D %37
+%39 = OpLoad %25 %38
+%40 = OpLoad %float %coords1D
+%41 = OpLoad %float %lod
+%46 = OpULessThan %bool %37 %uint_128
+OpSelectionMerge %47 None
+OpBranchConditional %46 %48 %49
+%48 = OpLabel
+%50 = OpLoad %25 %38
+%51 = OpImageSampleExplicitLod %v4float %50 %40 Lod %41
+OpBranch %47
+%49 = OpLabel
+%52 = OpBitcast %uint %37
+%105 = OpFunctionCall %void %53 %uint_74 %uint_0 %52 %uint_128
+OpBranch %47
+%47 = OpLabel
+%107 = OpPhi %v4float %51 %48 %106 %49
+%43 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+OpStore %43 %107
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output_func =
+ R"(%53 = OpFunction %void None %54
+%55 = OpFunctionParameter %uint
+%56 = OpFunctionParameter %uint
+%57 = OpFunctionParameter %uint
+%58 = OpFunctionParameter %uint
+%59 = OpLabel
+%65 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_0
+%68 = OpAtomicIAdd %uint %65 %uint_4 %uint_0 %uint_9
+%69 = OpIAdd %uint %68 %uint_9
+%70 = OpArrayLength %uint %63 1
+%71 = OpULessThanEqual %bool %69 %70
+OpSelectionMerge %72 None
+OpBranchConditional %71 %73 %72
+%73 = OpLabel
+%74 = OpIAdd %uint %68 %uint_0
+%75 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %74
+OpStore %75 %uint_9
+%77 = OpIAdd %uint %68 %uint_1
+%78 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %77
+OpStore %78 %uint_23
+%80 = OpIAdd %uint %68 %uint_2
+%81 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %80
+OpStore %81 %55
+%83 = OpIAdd %uint %68 %uint_3
+%84 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %83
+OpStore %84 %uint_0
+%87 = OpLoad %uint %gl_VertexIndex
+%88 = OpIAdd %uint %68 %uint_4
+%89 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %88
+OpStore %89 %87
+%91 = OpLoad %uint %gl_InstanceIndex
+%93 = OpIAdd %uint %68 %uint_5
+%94 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %93
+OpStore %94 %91
+%96 = OpIAdd %uint %68 %uint_6
+%97 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %96
+OpStore %97 %56
+%99 = OpIAdd %uint %68 %uint_7
+%100 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %99
+OpStore %100 %57
+%102 = OpIAdd %uint %68 %uint_8
+%103 = OpAccessChain %_ptr_StorageBuffer_uint %63 %uint_1 %102
+OpStore %103 %58
+OpBranch %72
+%72 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<InstBindlessCheckPass>(
+ defs_before + func_before, defs_after + func_after + output_func, true,
+ true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// TODO(greg-lunarg): Come up with cases to put here :)
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/instruction_list_test.cpp b/test/opt/instruction_list_test.cpp
new file mode 100644
index 0000000..e745790
--- /dev/null
+++ b/test/opt/instruction_list_test.cpp
@@ -0,0 +1,115 @@
+// 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 <algorithm>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/instruction.h"
+#include "source/opt/instruction_list.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::ContainerEq;
+using ::testing::ElementsAre;
+using InstructionListTest = ::testing::Test;
+
+// A class that overrides the destructor, so we can trace it.
+class TestInstruction : public Instruction {
+ public:
+ TestInstruction() : Instruction() { created_instructions_.push_back(this); }
+
+ ~TestInstruction() { deleted_instructions_.push_back(this); }
+
+ static std::vector<TestInstruction*> created_instructions_;
+ static std::vector<TestInstruction*> deleted_instructions_;
+};
+
+std::vector<TestInstruction*> TestInstruction::created_instructions_;
+std::vector<TestInstruction*> TestInstruction::deleted_instructions_;
+
+// Test that the destructor for InstructionList is calling the destructor
+// for every element that is in the list.
+TEST(InstructionListTest, Destructor) {
+ InstructionList* list = new InstructionList();
+ list->push_back(std::unique_ptr<Instruction>(new Instruction()));
+ list->push_back(std::unique_ptr<Instruction>(new Instruction()));
+ delete list;
+
+ // Sorting because we do not care if the order of create and destruction is
+ // the same. Using generic sort just incase things are changed above.
+ std::sort(TestInstruction::created_instructions_.begin(),
+ TestInstruction::created_instructions_.end());
+ std::sort(TestInstruction::deleted_instructions_.begin(),
+ TestInstruction::deleted_instructions_.end());
+ EXPECT_THAT(TestInstruction::created_instructions_,
+ ContainerEq(TestInstruction::deleted_instructions_));
+}
+
+// Test the |InsertBefore| with a single instruction in the iterator class.
+// Need to make sure the elements are inserted in the correct order, and the
+// return value points to the correct location.
+//
+// Comparing addresses to make sure they remain stable, so other data structures
+// can have pointers to instructions in InstructionList.
+TEST(InstructionListTest, InsertBefore1) {
+ InstructionList list;
+ std::vector<Instruction*> inserted_instructions;
+ for (int i = 0; i < 4; i++) {
+ std::unique_ptr<Instruction> inst(new Instruction());
+ inserted_instructions.push_back(inst.get());
+ auto new_element = list.end().InsertBefore(std::move(inst));
+ EXPECT_EQ(&*new_element, inserted_instructions.back());
+ }
+
+ std::vector<Instruction*> output;
+ for (auto& i : list) {
+ output.push_back(&i);
+ }
+ EXPECT_THAT(output, ContainerEq(inserted_instructions));
+}
+
+// Test inserting an entire vector of instructions using InsertBefore. Checking
+// the order of insertion and the return value.
+//
+// Comparing addresses to make sure they remain stable, so other data structures
+// can have pointers to instructions in InstructionList.
+TEST(InstructionListTest, InsertBefore2) {
+ InstructionList list;
+ std::vector<std::unique_ptr<Instruction>> new_instructions;
+ std::vector<Instruction*> created_instructions;
+ for (int i = 0; i < 4; i++) {
+ std::unique_ptr<Instruction> inst(new Instruction());
+ created_instructions.push_back(inst.get());
+ new_instructions.push_back(std::move(inst));
+ }
+ auto new_element = list.begin().InsertBefore(std::move(new_instructions));
+ EXPECT_TRUE(new_instructions.empty());
+ EXPECT_EQ(&*new_element, created_instructions.front());
+
+ std::vector<Instruction*> output;
+ for (auto& i : list) {
+ output.push_back(&i);
+ }
+ EXPECT_THAT(output, ContainerEq(created_instructions));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/instruction_test.cpp b/test/opt/instruction_test.cpp
new file mode 100644
index 0000000..a697201
--- /dev/null
+++ b/test/opt/instruction_test.cpp
@@ -0,0 +1,1135 @@
+// Copyright (c) 2016 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/instruction.h"
+#include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+#include "test/unit_spirv.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using spvtest::MakeInstruction;
+using ::testing::Eq;
+using DescriptorTypeTest = PassTest<::testing::Test>;
+using OpaqueTypeTest = PassTest<::testing::Test>;
+using GetBaseTest = PassTest<::testing::Test>;
+using ValidBasePointerTest = PassTest<::testing::Test>;
+
+TEST(InstructionTest, CreateTrivial) {
+ Instruction empty;
+ EXPECT_EQ(SpvOpNop, empty.opcode());
+ EXPECT_EQ(0u, empty.type_id());
+ EXPECT_EQ(0u, empty.result_id());
+ EXPECT_EQ(0u, empty.NumOperands());
+ EXPECT_EQ(0u, empty.NumOperandWords());
+ EXPECT_EQ(0u, empty.NumInOperandWords());
+ EXPECT_EQ(empty.cend(), empty.cbegin());
+ EXPECT_EQ(empty.end(), empty.begin());
+}
+
+TEST(InstructionTest, CreateWithOpcodeAndNoOperands) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, SpvOpReturn);
+ EXPECT_EQ(SpvOpReturn, inst.opcode());
+ EXPECT_EQ(0u, inst.type_id());
+ EXPECT_EQ(0u, inst.result_id());
+ EXPECT_EQ(0u, inst.NumOperands());
+ EXPECT_EQ(0u, inst.NumOperandWords());
+ EXPECT_EQ(0u, inst.NumInOperandWords());
+ EXPECT_EQ(inst.cend(), inst.cbegin());
+ EXPECT_EQ(inst.end(), inst.begin());
+}
+
+// The words for an OpTypeInt for 32-bit signed integer resulting in Id 44.
+uint32_t kSampleInstructionWords[] = {(4 << 16) | uint32_t(SpvOpTypeInt), 44,
+ 32, 1};
+// The operands that would be parsed from kSampleInstructionWords
+spv_parsed_operand_t kSampleParsedOperands[] = {
+ {1, 1, SPV_OPERAND_TYPE_RESULT_ID, SPV_NUMBER_NONE, 0},
+ {2, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT, 32},
+ {3, 1, SPV_OPERAND_TYPE_LITERAL_INTEGER, SPV_NUMBER_UNSIGNED_INT, 1},
+};
+
+// A valid parse of kSampleParsedOperands.
+spv_parsed_instruction_t kSampleParsedInstruction = {kSampleInstructionWords,
+ uint16_t(4),
+ uint16_t(SpvOpTypeInt),
+ SPV_EXT_INST_TYPE_NONE,
+ 0, // type id
+ 44, // result id
+ kSampleParsedOperands,
+ 3};
+
+// The words for an OpAccessChain instruction.
+uint32_t kSampleAccessChainInstructionWords[] = {
+ (7 << 16) | uint32_t(SpvOpAccessChain), 100, 101, 102, 103, 104, 105};
+
+// The operands that would be parsed from kSampleAccessChainInstructionWords.
+spv_parsed_operand_t kSampleAccessChainOperands[] = {
+ {1, 1, SPV_OPERAND_TYPE_RESULT_ID, SPV_NUMBER_NONE, 0},
+ {2, 1, SPV_OPERAND_TYPE_TYPE_ID, SPV_NUMBER_NONE, 0},
+ {3, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0},
+ {4, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0},
+ {5, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0},
+ {6, 1, SPV_OPERAND_TYPE_ID, SPV_NUMBER_NONE, 0},
+};
+
+// A valid parse of kSampleAccessChainInstructionWords
+spv_parsed_instruction_t kSampleAccessChainInstruction = {
+ kSampleAccessChainInstructionWords,
+ uint16_t(7),
+ uint16_t(SpvOpAccessChain),
+ SPV_EXT_INST_TYPE_NONE,
+ 100, // type id
+ 101, // result id
+ kSampleAccessChainOperands,
+ 6};
+
+// The words for an OpControlBarrier instruction.
+uint32_t kSampleControlBarrierInstructionWords[] = {
+ (4 << 16) | uint32_t(SpvOpControlBarrier), 100, 101, 102};
+
+// The operands that would be parsed from kSampleControlBarrierInstructionWords.
+spv_parsed_operand_t kSampleControlBarrierOperands[] = {
+ {1, 1, SPV_OPERAND_TYPE_SCOPE_ID, SPV_NUMBER_NONE, 0}, // Execution
+ {2, 1, SPV_OPERAND_TYPE_SCOPE_ID, SPV_NUMBER_NONE, 0}, // Memory
+ {3, 1, SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, SPV_NUMBER_NONE,
+ 0}, // Semantics
+};
+
+// A valid parse of kSampleControlBarrierInstructionWords
+spv_parsed_instruction_t kSampleControlBarrierInstruction = {
+ kSampleControlBarrierInstructionWords,
+ uint16_t(4),
+ uint16_t(SpvOpControlBarrier),
+ SPV_EXT_INST_TYPE_NONE,
+ 0, // type id
+ 0, // result id
+ kSampleControlBarrierOperands,
+ 3};
+
+TEST(InstructionTest, CreateWithOpcodeAndOperands) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, kSampleParsedInstruction);
+ EXPECT_EQ(SpvOpTypeInt, inst.opcode());
+ EXPECT_EQ(0u, inst.type_id());
+ EXPECT_EQ(44u, inst.result_id());
+ EXPECT_EQ(3u, inst.NumOperands());
+ EXPECT_EQ(3u, inst.NumOperandWords());
+ EXPECT_EQ(2u, inst.NumInOperandWords());
+}
+
+TEST(InstructionTest, GetOperand) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, kSampleParsedInstruction);
+ EXPECT_THAT(inst.GetOperand(0).words, Eq(std::vector<uint32_t>{44}));
+ EXPECT_THAT(inst.GetOperand(1).words, Eq(std::vector<uint32_t>{32}));
+ EXPECT_THAT(inst.GetOperand(2).words, Eq(std::vector<uint32_t>{1}));
+}
+
+TEST(InstructionTest, GetInOperand) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, kSampleParsedInstruction);
+ EXPECT_THAT(inst.GetInOperand(0).words, Eq(std::vector<uint32_t>{32}));
+ EXPECT_THAT(inst.GetInOperand(1).words, Eq(std::vector<uint32_t>{1}));
+}
+
+TEST(InstructionTest, OperandConstIterators) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, kSampleParsedInstruction);
+ // Spot check iteration across operands.
+ auto cbegin = inst.cbegin();
+ auto cend = inst.cend();
+ EXPECT_NE(cend, inst.cbegin());
+
+ auto citer = inst.cbegin();
+ for (int i = 0; i < 3; ++i, ++citer) {
+ const auto& operand = *citer;
+ EXPECT_THAT(operand.type, Eq(kSampleParsedOperands[i].type));
+ EXPECT_THAT(operand.words,
+ Eq(std::vector<uint32_t>{kSampleInstructionWords[i + 1]}));
+ EXPECT_NE(cend, citer);
+ }
+ EXPECT_EQ(cend, citer);
+
+ // Check that cbegin and cend have not changed.
+ EXPECT_EQ(cbegin, inst.cbegin());
+ EXPECT_EQ(cend, inst.cend());
+
+ // Check arithmetic.
+ const Operand& operand2 = *(inst.cbegin() + 2);
+ EXPECT_EQ(SPV_OPERAND_TYPE_LITERAL_INTEGER, operand2.type);
+}
+
+TEST(InstructionTest, OperandIterators) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, kSampleParsedInstruction);
+ // Spot check iteration across operands, with mutable iterators.
+ auto begin = inst.begin();
+ auto end = inst.end();
+ EXPECT_NE(end, inst.begin());
+
+ auto iter = inst.begin();
+ for (int i = 0; i < 3; ++i, ++iter) {
+ const auto& operand = *iter;
+ EXPECT_THAT(operand.type, Eq(kSampleParsedOperands[i].type));
+ EXPECT_THAT(operand.words,
+ Eq(std::vector<uint32_t>{kSampleInstructionWords[i + 1]}));
+ EXPECT_NE(end, iter);
+ }
+ EXPECT_EQ(end, iter);
+
+ // Check that begin and end have not changed.
+ EXPECT_EQ(begin, inst.begin());
+ EXPECT_EQ(end, inst.end());
+
+ // Check arithmetic.
+ Operand& operand2 = *(inst.begin() + 2);
+ EXPECT_EQ(SPV_OPERAND_TYPE_LITERAL_INTEGER, operand2.type);
+
+ // Check mutation through an iterator.
+ operand2.type = SPV_OPERAND_TYPE_TYPE_ID;
+ EXPECT_EQ(SPV_OPERAND_TYPE_TYPE_ID, (*(inst.cbegin() + 2)).type);
+}
+
+TEST(InstructionTest, ForInIdStandardIdTypes) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, kSampleAccessChainInstruction);
+
+ std::vector<uint32_t> ids;
+ inst.ForEachInId([&ids](const uint32_t* idptr) { ids.push_back(*idptr); });
+ EXPECT_THAT(ids, Eq(std::vector<uint32_t>{102, 103, 104, 105}));
+
+ ids.clear();
+ inst.ForEachInId([&ids](uint32_t* idptr) { ids.push_back(*idptr); });
+ EXPECT_THAT(ids, Eq(std::vector<uint32_t>{102, 103, 104, 105}));
+}
+
+TEST(InstructionTest, ForInIdNonstandardIdTypes) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context, kSampleControlBarrierInstruction);
+
+ std::vector<uint32_t> ids;
+ inst.ForEachInId([&ids](const uint32_t* idptr) { ids.push_back(*idptr); });
+ EXPECT_THAT(ids, Eq(std::vector<uint32_t>{100, 101, 102}));
+
+ ids.clear();
+ inst.ForEachInId([&ids](uint32_t* idptr) { ids.push_back(*idptr); });
+ EXPECT_THAT(ids, Eq(std::vector<uint32_t>{100, 101, 102}));
+}
+
+TEST(InstructionTest, UniqueIds) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst1(&context);
+ Instruction inst2(&context);
+ EXPECT_NE(inst1.unique_id(), inst2.unique_id());
+}
+
+TEST(InstructionTest, CloneUniqueIdDifferent) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&context);
+ std::unique_ptr<Instruction> clone(inst.Clone(&context));
+ EXPECT_EQ(inst.context(), clone->context());
+ EXPECT_NE(inst.unique_id(), clone->unique_id());
+}
+
+TEST(InstructionTest, CloneDifferentContext) {
+ IRContext c1(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ IRContext c2(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&c1);
+ std::unique_ptr<Instruction> clone(inst.Clone(&c2));
+ EXPECT_EQ(&c1, inst.context());
+ EXPECT_EQ(&c2, clone->context());
+ EXPECT_NE(&c1, &c2);
+}
+
+TEST(InstructionTest, CloneDifferentContextDifferentUniqueId) {
+ IRContext c1(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ IRContext c2(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction inst(&c1);
+ Instruction other(&c2);
+ std::unique_ptr<Instruction> clone(inst.Clone(&c2));
+ EXPECT_EQ(&c2, clone->context());
+ EXPECT_NE(other.unique_id(), clone->unique_id());
+}
+
+TEST(InstructionTest, EqualsEqualsOperator) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction i1(&context);
+ Instruction i2(&context);
+ std::unique_ptr<Instruction> clone(i1.Clone(&context));
+ EXPECT_TRUE(i1 == i1);
+ EXPECT_FALSE(i1 == i2);
+ EXPECT_FALSE(i1 == *clone);
+ EXPECT_FALSE(i2 == *clone);
+}
+
+TEST(InstructionTest, LessThanOperator) {
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ Instruction i1(&context);
+ Instruction i2(&context);
+ std::unique_ptr<Instruction> clone(i1.Clone(&context));
+ EXPECT_TRUE(i1 < i2);
+ EXPECT_TRUE(i1 < *clone);
+ EXPECT_TRUE(i2 < *clone);
+}
+
+TEST_F(DescriptorTypeTest, StorageImage) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeFloat 32
+ %7 = OpTypeImage %6 2D 0 0 0 2 R32f
+ %8 = OpTypePointer UniformConstant %7
+ %3 = OpVariable %8 UniformConstant
+ %2 = OpFunction %4 None %5
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* type = context->get_def_use_mgr()->GetDef(8);
+ EXPECT_TRUE(type->IsVulkanStorageImage());
+ EXPECT_FALSE(type->IsVulkanSampledImage());
+ EXPECT_FALSE(type->IsVulkanStorageTexelBuffer());
+ EXPECT_FALSE(type->IsVulkanStorageBuffer());
+ EXPECT_FALSE(type->IsVulkanUniformBuffer());
+
+ Instruction* variable = context->get_def_use_mgr()->GetDef(3);
+ EXPECT_FALSE(variable->IsReadOnlyVariable());
+}
+
+TEST_F(DescriptorTypeTest, SampledImage) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeFloat 32
+ %7 = OpTypeImage %6 2D 0 0 0 1 Unknown
+ %8 = OpTypePointer UniformConstant %7
+ %3 = OpVariable %8 UniformConstant
+ %2 = OpFunction %4 None %5
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* type = context->get_def_use_mgr()->GetDef(8);
+ EXPECT_FALSE(type->IsVulkanStorageImage());
+ EXPECT_TRUE(type->IsVulkanSampledImage());
+ EXPECT_FALSE(type->IsVulkanStorageTexelBuffer());
+ EXPECT_FALSE(type->IsVulkanStorageBuffer());
+ EXPECT_FALSE(type->IsVulkanUniformBuffer());
+
+ Instruction* variable = context->get_def_use_mgr()->GetDef(3);
+ EXPECT_TRUE(variable->IsReadOnlyVariable());
+}
+
+TEST_F(DescriptorTypeTest, StorageTexelBuffer) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeFloat 32
+ %7 = OpTypeImage %6 Buffer 0 0 0 2 R32f
+ %8 = OpTypePointer UniformConstant %7
+ %3 = OpVariable %8 UniformConstant
+ %2 = OpFunction %4 None %5
+ %9 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* type = context->get_def_use_mgr()->GetDef(8);
+ EXPECT_FALSE(type->IsVulkanStorageImage());
+ EXPECT_FALSE(type->IsVulkanSampledImage());
+ EXPECT_TRUE(type->IsVulkanStorageTexelBuffer());
+ EXPECT_FALSE(type->IsVulkanStorageBuffer());
+ EXPECT_FALSE(type->IsVulkanUniformBuffer());
+
+ Instruction* variable = context->get_def_use_mgr()->GetDef(3);
+ EXPECT_FALSE(variable->IsReadOnlyVariable());
+}
+
+TEST_F(DescriptorTypeTest, StorageBuffer) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ OpDecorate %9 BufferBlock
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypeRuntimeArray %7
+ %9 = OpTypeStruct %8
+ %10 = OpTypePointer Uniform %9
+ %3 = OpVariable %10 Uniform
+ %2 = OpFunction %4 None %5
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* type = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_FALSE(type->IsVulkanStorageImage());
+ EXPECT_FALSE(type->IsVulkanSampledImage());
+ EXPECT_FALSE(type->IsVulkanStorageTexelBuffer());
+ EXPECT_TRUE(type->IsVulkanStorageBuffer());
+ EXPECT_FALSE(type->IsVulkanUniformBuffer());
+
+ Instruction* variable = context->get_def_use_mgr()->GetDef(3);
+ EXPECT_FALSE(variable->IsReadOnlyVariable());
+}
+
+TEST_F(DescriptorTypeTest, UniformBuffer) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ OpDecorate %9 Block
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypeRuntimeArray %7
+ %9 = OpTypeStruct %8
+ %10 = OpTypePointer Uniform %9
+ %3 = OpVariable %10 Uniform
+ %2 = OpFunction %4 None %5
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* type = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_FALSE(type->IsVulkanStorageImage());
+ EXPECT_FALSE(type->IsVulkanSampledImage());
+ EXPECT_FALSE(type->IsVulkanStorageTexelBuffer());
+ EXPECT_FALSE(type->IsVulkanStorageBuffer());
+ EXPECT_TRUE(type->IsVulkanUniformBuffer());
+
+ Instruction* variable = context->get_def_use_mgr()->GetDef(3);
+ EXPECT_TRUE(variable->IsReadOnlyVariable());
+}
+
+TEST_F(DescriptorTypeTest, NonWritableIsReadOnly) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ OpDecorate %9 BufferBlock
+ OpDecorate %3 NonWritable
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypeRuntimeArray %7
+ %9 = OpTypeStruct %8
+ %10 = OpTypePointer Uniform %9
+ %3 = OpVariable %10 Uniform
+ %2 = OpFunction %4 None %5
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* variable = context->get_def_use_mgr()->GetDef(3);
+ EXPECT_TRUE(variable->IsReadOnlyVariable());
+}
+
+TEST_F(OpaqueTypeTest, BaseOpaqueTypesShader) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypeImage %5 2D 1 0 0 1 Unknown
+ %7 = OpTypeSampler
+ %8 = OpTypeSampledImage %6
+ %9 = OpTypeRuntimeArray %5
+ %2 = OpFunction %3 None %4
+ %10 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* image_type = context->get_def_use_mgr()->GetDef(6);
+ EXPECT_TRUE(image_type->IsOpaqueType());
+ Instruction* sampler_type = context->get_def_use_mgr()->GetDef(7);
+ EXPECT_TRUE(sampler_type->IsOpaqueType());
+ Instruction* sampled_image_type = context->get_def_use_mgr()->GetDef(8);
+ EXPECT_TRUE(sampled_image_type->IsOpaqueType());
+ Instruction* runtime_array_type = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_TRUE(runtime_array_type->IsOpaqueType());
+ Instruction* float_type = context->get_def_use_mgr()->GetDef(5);
+ EXPECT_FALSE(float_type->IsOpaqueType());
+ Instruction* void_type = context->get_def_use_mgr()->GetDef(3);
+ EXPECT_FALSE(void_type->IsOpaqueType());
+}
+
+TEST_F(OpaqueTypeTest, OpaqueStructTypes) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypeRuntimeArray %5
+ %7 = OpTypeStruct %6 %6
+ %8 = OpTypeStruct %5 %6
+ %9 = OpTypeStruct %6 %5
+ %10 = OpTypeStruct %7
+ %2 = OpFunction %3 None %4
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ for (int i = 7; i <= 10; i++) {
+ Instruction* type = context->get_def_use_mgr()->GetDef(i);
+ EXPECT_TRUE(type->IsOpaqueType());
+ }
+}
+
+TEST_F(GetBaseTest, SampleImage) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 2
+ %8 = OpTypeVector %6 4
+ %9 = OpConstant %6 0
+ %10 = OpConstantComposite %7 %9 %9
+ %11 = OpTypeImage %6 2D 0 0 0 1 R32f
+ %12 = OpTypePointer UniformConstant %11
+ %3 = OpVariable %12 UniformConstant
+ %13 = OpTypeSampledImage %11
+ %14 = OpTypeSampler
+ %15 = OpTypePointer UniformConstant %14
+ %16 = OpVariable %15 UniformConstant
+ %2 = OpFunction %4 None %5
+ %17 = OpLabel
+ %18 = OpLoad %11 %3
+ %19 = OpLoad %14 %16
+ %20 = OpSampledImage %13 %18 %19
+ %21 = OpImageSampleImplicitLod %8 %20 %10
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* load = context->get_def_use_mgr()->GetDef(21);
+ Instruction* base = context->get_def_use_mgr()->GetDef(20);
+ EXPECT_TRUE(load->GetBaseAddress() == base);
+}
+
+TEST_F(GetBaseTest, PtrAccessChain) {
+ const std::string text = R"(
+ OpCapability VariablePointers
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "PSMain" %2
+ OpExecutionMode %1 OriginUpperLeft
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %int = OpTypeInt 32 8388353
+ %int_0 = OpConstant %int 0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+ %2 = OpVariable %_ptr_Function_v4float Input
+ %1 = OpFunction %void None %4
+ %10 = OpLabel
+ %11 = OpPtrAccessChain %_ptr_Function_v4float %2 %int_0
+ %12 = OpLoad %v4float %11
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* load = context->get_def_use_mgr()->GetDef(12);
+ Instruction* base = context->get_def_use_mgr()->GetDef(2);
+ EXPECT_TRUE(load->GetBaseAddress() == base);
+}
+
+TEST_F(GetBaseTest, ImageRead) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "myStorageImage"
+ OpDecorate %3 DescriptorSet 0
+ OpDecorate %3 Binding 0
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeInt 32 0
+ %7 = OpTypeVector %6 2
+ %8 = OpConstant %6 0
+ %9 = OpConstantComposite %7 %8 %8
+ %10 = OpTypeImage %6 2D 0 0 0 2 R32f
+ %11 = OpTypePointer UniformConstant %10
+ %3 = OpVariable %11 UniformConstant
+ %2 = OpFunction %4 None %5
+ %12 = OpLabel
+ %13 = OpLoad %10 %3
+ %14 = OpImageRead %6 %13 %9
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ Instruction* load = context->get_def_use_mgr()->GetDef(14);
+ Instruction* base = context->get_def_use_mgr()->GetDef(13);
+ EXPECT_TRUE(load->GetBaseAddress() == base);
+}
+
+TEST_F(ValidBasePointerTest, OpSelectBadNoVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpVariable %4 StorageBuffer
+%6 = OpTypeFunction %2
+%7 = OpTypeBool
+%8 = OpConstantTrue %7
+%1 = OpFunction %2 None %6
+%9 = OpLabel
+%10 = OpSelect %4 %8 %5 %5
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* select = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_NE(select, nullptr);
+ EXPECT_FALSE(select->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpSelectBadNoVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpVariable %4 Workgroup
+%6 = OpTypeFunction %2
+%7 = OpTypeBool
+%8 = OpConstantTrue %7
+%1 = OpFunction %2 None %6
+%9 = OpLabel
+%10 = OpSelect %4 %8 %5 %5
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* select = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_NE(select, nullptr);
+ EXPECT_FALSE(select->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpSelectGoodVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpVariable %4 StorageBuffer
+%6 = OpTypeFunction %2
+%7 = OpTypeBool
+%8 = OpConstantTrue %7
+%1 = OpFunction %2 None %6
+%9 = OpLabel
+%10 = OpSelect %4 %8 %5 %5
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* select = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_NE(select, nullptr);
+ EXPECT_TRUE(select->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpSelectGoodVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointers
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpVariable %4 Workgroup
+%6 = OpTypeFunction %2
+%7 = OpTypeBool
+%8 = OpConstantTrue %7
+%1 = OpFunction %2 None %6
+%9 = OpLabel
+%10 = OpSelect %4 %8 %5 %5
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* select = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_NE(select, nullptr);
+ EXPECT_TRUE(select->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpConstantNullBadNoVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(5);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_FALSE(null_inst->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpConstantNullBadNoVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(5);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_FALSE(null_inst->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpConstantNullGoodVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(5);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_TRUE(null_inst->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpConstantNullGoodVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointers
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%7 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(5);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_TRUE(null_inst->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpPhiBadNoVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpVariable %4 StorageBuffer
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+%9 = OpPhi %4 %5 %7
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* phi = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(phi, nullptr);
+ EXPECT_FALSE(phi->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpPhiBadNoVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpVariable %4 Workgroup
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+%9 = OpPhi %4 %5 %7
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* phi = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(phi, nullptr);
+ EXPECT_FALSE(phi->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpPhiGoodVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpVariable %4 StorageBuffer
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+%9 = OpPhi %4 %5 %7
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* phi = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(phi, nullptr);
+ EXPECT_TRUE(phi->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpPhiGoodVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointers
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpVariable %4 Workgroup
+%6 = OpTypeFunction %2
+%1 = OpFunction %2 None %6
+%7 = OpLabel
+OpBranch %8
+%8 = OpLabel
+%9 = OpPhi %4 %5 %7
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* phi = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(phi, nullptr);
+ EXPECT_TRUE(phi->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpFunctionCallBadNoVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%7 = OpTypeFunction %4
+%1 = OpFunction %2 None %6
+%8 = OpLabel
+%9 = OpFunctionCall %4 %10
+OpReturn
+OpFunctionEnd
+%10 = OpFunction %4 None %7
+%11 = OpLabel
+OpReturnValue %5
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_FALSE(null_inst->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpFunctionCallBadNoVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%7 = OpTypeFunction %4
+%1 = OpFunction %2 None %6
+%8 = OpLabel
+%9 = OpFunctionCall %4 %10
+OpReturn
+OpFunctionEnd
+%10 = OpFunction %4 None %7
+%11 = OpLabel
+OpReturnValue %5
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_FALSE(null_inst->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpFunctionCallGoodVariablePointersStorageBuffer) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointersStorageBuffer
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer StorageBuffer %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%7 = OpTypeFunction %4
+%1 = OpFunction %2 None %6
+%8 = OpLabel
+%9 = OpFunctionCall %4 %10
+OpReturn
+OpFunctionEnd
+%10 = OpFunction %4 None %7
+%11 = OpLabel
+OpReturnValue %5
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_TRUE(null_inst->IsValidBasePointer());
+}
+
+TEST_F(ValidBasePointerTest, OpFunctionCallGoodVariablePointers) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability VariablePointers
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "func"
+%2 = OpTypeVoid
+%3 = OpTypeInt 32 0
+%4 = OpTypePointer Workgroup %3
+%5 = OpConstantNull %4
+%6 = OpTypeFunction %2
+%7 = OpTypeFunction %4
+%1 = OpFunction %2 None %6
+%8 = OpLabel
+%9 = OpFunctionCall %4 %10
+OpReturn
+OpFunctionEnd
+%10 = OpFunction %4 None %7
+%11 = OpLabel
+OpReturnValue %5
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ Instruction* null_inst = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_NE(null_inst, nullptr);
+ EXPECT_TRUE(null_inst->IsValidBasePointer());
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/ir_builder.cpp b/test/opt/ir_builder.cpp
new file mode 100644
index 0000000..f800ca4
--- /dev/null
+++ b/test/opt/ir_builder.cpp
@@ -0,0 +1,439 @@
+// Copyright (c) 2018 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 <algorithm>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/basic_block.h"
+#include "source/opt/build_module.h"
+#include "source/opt/instruction.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/type_manager.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using Analysis = IRContext::Analysis;
+using IRBuilderTest = ::testing::Test;
+
+bool Validate(const std::vector<uint32_t>& bin) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {bin.data(), bin.size()};
+ spv_result_t error = spvValidate(spvContext, &binary, &diagnostic);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ return error == 0;
+}
+
+void Match(const std::string& original, IRContext* context,
+ bool do_validation = true) {
+ std::vector<uint32_t> bin;
+ context->module()->ToBinary(&bin, true);
+ if (do_validation) {
+ EXPECT_TRUE(Validate(bin));
+ }
+ std::string assembly;
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_2);
+ EXPECT_TRUE(
+ tools.Disassemble(bin, &assembly, SpirvTools::kDefaultDisassembleOption))
+ << "Disassembling failed for shader:\n"
+ << assembly << std::endl;
+ auto match_result = effcee::Match(assembly, original);
+ EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+ << match_result.message() << "\nChecking result:\n"
+ << assembly;
+}
+
+TEST_F(IRBuilderTest, TestInsnAddition) {
+ const std::string text = R"(
+; CHECK: %18 = OpLabel
+; CHECK: OpPhi %int %int_0 %14
+; CHECK: OpPhi %bool %16 %14
+; CHECK: OpBranch %17
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeInt 32 1
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %10 = OpTypeBool
+ %11 = OpTypeFloat 32
+ %12 = OpTypeVector %11 4
+ %13 = OpTypePointer Output %12
+ %3 = OpVariable %13 Output
+ %2 = OpFunction %5 None %6
+ %14 = OpLabel
+ %4 = OpVariable %8 Function
+ OpStore %4 %9
+ %15 = OpLoad %7 %4
+ %16 = OpINotEqual %10 %15 %9
+ OpSelectionMerge %17 None
+ OpBranchConditional %16 %18 %17
+ %18 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ BasicBlock* bb = context->cfg()->block(18);
+
+ // Build managers.
+ context->get_def_use_mgr();
+ context->get_instr_block(nullptr);
+
+ InstructionBuilder builder(context.get(), &*bb->begin());
+ Instruction* phi1 = builder.AddPhi(7, {9, 14});
+ Instruction* phi2 = builder.AddPhi(10, {16, 14});
+
+ // Make sure the InstructionBuilder did not update the def/use manager.
+ EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr);
+ EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr);
+ EXPECT_EQ(context->get_instr_block(phi1), nullptr);
+ EXPECT_EQ(context->get_instr_block(phi2), nullptr);
+
+ Match(text, context.get());
+ }
+
+ {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build managers.
+ context->get_def_use_mgr();
+ context->get_instr_block(nullptr);
+
+ BasicBlock* bb = context->cfg()->block(18);
+ InstructionBuilder builder(
+ context.get(), &*bb->begin(),
+ IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
+ Instruction* phi1 = builder.AddPhi(7, {9, 14});
+ Instruction* phi2 = builder.AddPhi(10, {16, 14});
+
+ // Make sure InstructionBuilder updated the def/use manager
+ EXPECT_NE(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr);
+ EXPECT_NE(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr);
+ EXPECT_NE(context->get_instr_block(phi1), nullptr);
+ EXPECT_NE(context->get_instr_block(phi2), nullptr);
+
+ Match(text, context.get());
+ }
+}
+
+TEST_F(IRBuilderTest, TestCondBranchAddition) {
+ const std::string text = R"(
+; CHECK: %main = OpFunction %void None %6
+; CHECK-NEXT: %15 = OpLabel
+; CHECK-NEXT: OpSelectionMerge %13 None
+; CHECK-NEXT: OpBranchConditional %true %14 %13
+; CHECK-NEXT: %14 = OpLabel
+; CHECK-NEXT: OpBranch %13
+; CHECK-NEXT: %13 = OpLabel
+; CHECK-NEXT: OpReturn
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeBool
+ %8 = OpTypePointer Private %7
+ %9 = OpConstantTrue %7
+ %10 = OpTypeFloat 32
+ %11 = OpTypeVector %10 4
+ %12 = OpTypePointer Output %11
+ %3 = OpVariable %12 Output
+ %4 = OpVariable %8 Private
+ %2 = OpFunction %5 None %6
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ Function& fn = *context->module()->begin();
+
+ BasicBlock& bb_merge = *fn.begin();
+
+ // TODO(1841): Handle id overflow.
+ fn.begin().InsertBefore(std::unique_ptr<BasicBlock>(
+ new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
+ context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
+ BasicBlock& bb_true = *fn.begin();
+ {
+ InstructionBuilder builder(context.get(), &*bb_true.begin());
+ builder.AddBranch(bb_merge.id());
+ }
+
+ // TODO(1841): Handle id overflow.
+ fn.begin().InsertBefore(std::unique_ptr<BasicBlock>(
+ new BasicBlock(std::unique_ptr<Instruction>(new Instruction(
+ context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
+ BasicBlock& bb_cond = *fn.begin();
+
+ InstructionBuilder builder(context.get(), &bb_cond);
+ // This also test consecutive instruction insertion: merge selection +
+ // branch.
+ builder.AddConditionalBranch(9, bb_true.id(), bb_merge.id(), bb_merge.id());
+
+ Match(text, context.get());
+ }
+}
+
+TEST_F(IRBuilderTest, AddSelect) {
+ const std::string text = R"(
+; CHECK: [[bool:%\w+]] = OpTypeBool
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[true:%\w+]] = OpConstantTrue [[bool]]
+; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1
+; CHECK: OpSelect [[uint]] [[true]] [[u0]] [[u1]]
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpConstantTrue %2
+%5 = OpConstant %3 0
+%6 = OpConstant %3 1
+%7 = OpTypeFunction %1
+%8 = OpFunction %1 None %7
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(nullptr, context);
+
+ InstructionBuilder builder(context.get(),
+ &*context->module()->begin()->begin()->begin());
+ EXPECT_NE(nullptr, builder.AddSelect(3u, 4u, 5u, 6u));
+
+ Match(text, context.get());
+}
+
+TEST_F(IRBuilderTest, AddCompositeConstruct) {
+ const std::string text = R"(
+; CHECK: [[uint:%\w+]] = OpTypeInt
+; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[uint]] [[uint]] [[uint]] [[uint]]
+; CHECK: OpCompositeConstruct [[struct]] [[u0]] [[u1]] [[u1]] [[u0]]
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpConstant %2 0
+%4 = OpConstant %2 1
+%5 = OpTypeStruct %2 %2 %2 %2
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(nullptr, context);
+
+ InstructionBuilder builder(context.get(),
+ &*context->module()->begin()->begin()->begin());
+ std::vector<uint32_t> ids = {3u, 4u, 4u, 3u};
+ EXPECT_NE(nullptr, builder.AddCompositeConstruct(5u, ids));
+
+ Match(text, context.get());
+}
+
+TEST_F(IRBuilderTest, ConstantAdder) {
+ const std::string text = R"(
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: OpConstant [[uint]] 13
+; CHECK: [[sint:%\w+]] = OpTypeInt 32 1
+; CHECK: OpConstant [[sint]] -1
+; CHECK: OpConstant [[uint]] 1
+; CHECK: OpConstant [[sint]] 34
+; CHECK: OpConstant [[uint]] 0
+; CHECK: OpConstant [[sint]] 0
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(nullptr, context);
+
+ InstructionBuilder builder(context.get(),
+ &*context->module()->begin()->begin()->begin());
+ EXPECT_NE(nullptr, builder.GetUintConstant(13));
+ EXPECT_NE(nullptr, builder.GetSintConstant(-1));
+
+ // Try adding the same constants again to make sure they aren't added.
+ EXPECT_NE(nullptr, builder.GetUintConstant(13));
+ EXPECT_NE(nullptr, builder.GetSintConstant(-1));
+
+ // Try adding different constants to make sure the type is reused.
+ EXPECT_NE(nullptr, builder.GetUintConstant(1));
+ EXPECT_NE(nullptr, builder.GetSintConstant(34));
+
+ // Try adding 0 as both signed and unsigned.
+ EXPECT_NE(nullptr, builder.GetUintConstant(0));
+ EXPECT_NE(nullptr, builder.GetSintConstant(0));
+
+ Match(text, context.get());
+}
+
+TEST_F(IRBuilderTest, ConstantAdderTypeAlreadyExists) {
+ const std::string text = R"(
+; CHECK: OpConstant %uint 13
+; CHECK: OpConstant %int -1
+; CHECK: OpConstant %uint 1
+; CHECK: OpConstant %int 34
+; CHECK: OpConstant %uint 0
+; CHECK: OpConstant %int 0
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%uint = OpTypeInt 32 0
+%int = OpTypeInt 32 1
+%4 = OpTypeFunction %1
+%5 = OpFunction %1 None %4
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(nullptr, context);
+
+ InstructionBuilder builder(context.get(),
+ &*context->module()->begin()->begin()->begin());
+ Instruction* const_1 = builder.GetUintConstant(13);
+ Instruction* const_2 = builder.GetSintConstant(-1);
+
+ EXPECT_NE(nullptr, const_1);
+ EXPECT_NE(nullptr, const_2);
+
+ // Try adding the same constants again to make sure they aren't added.
+ EXPECT_EQ(const_1, builder.GetUintConstant(13));
+ EXPECT_EQ(const_2, builder.GetSintConstant(-1));
+
+ Instruction* const_3 = builder.GetUintConstant(1);
+ Instruction* const_4 = builder.GetSintConstant(34);
+
+ // Try adding different constants to make sure the type is reused.
+ EXPECT_NE(nullptr, const_3);
+ EXPECT_NE(nullptr, const_4);
+
+ Instruction* const_5 = builder.GetUintConstant(0);
+ Instruction* const_6 = builder.GetSintConstant(0);
+
+ // Try adding 0 as both signed and unsigned.
+ EXPECT_NE(nullptr, const_5);
+ EXPECT_NE(nullptr, const_6);
+
+ // They have the same value but different types so should be unique.
+ EXPECT_NE(const_5, const_6);
+
+ // Check the types are correct.
+ uint32_t type_id_unsigned = const_1->GetSingleWordOperand(0);
+ uint32_t type_id_signed = const_2->GetSingleWordOperand(0);
+
+ EXPECT_NE(type_id_unsigned, type_id_signed);
+
+ EXPECT_EQ(const_3->GetSingleWordOperand(0), type_id_unsigned);
+ EXPECT_EQ(const_5->GetSingleWordOperand(0), type_id_unsigned);
+
+ EXPECT_EQ(const_4->GetSingleWordOperand(0), type_id_signed);
+ EXPECT_EQ(const_6->GetSingleWordOperand(0), type_id_signed);
+
+ Match(text, context.get());
+}
+
+TEST_F(IRBuilderTest, AccelerationStructureNV) {
+ const std::string text = R"(
+; CHECK: OpTypeAccelerationStructureNV
+OpCapability Shader
+OpCapability RayTracingNV
+OpExtension "SPV_NV_ray_tracing"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %8 "main"
+OpExecutionMode %8 OriginUpperLeft
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeAccelerationStructureNV
+%7 = OpTypeFunction %1
+%8 = OpFunction %1 None %7
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(nullptr, context);
+
+ InstructionBuilder builder(context.get(),
+ &*context->module()->begin()->begin()->begin());
+ Match(text, context.get());
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/ir_context_test.cpp b/test/opt/ir_context_test.cpp
new file mode 100644
index 0000000..4e2f5b2
--- /dev/null
+++ b/test/opt/ir_context_test.cpp
@@ -0,0 +1,669 @@
+// 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 <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/pass.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using Analysis = IRContext::Analysis;
+using ::testing::Each;
+using ::testing::UnorderedElementsAre;
+
+class DummyPassPreservesNothing : public Pass {
+ public:
+ DummyPassPreservesNothing(Status s) : Pass(), status_to_return_(s) {}
+
+ const char* name() const override { return "dummy-pass"; }
+ Status Process() override { return status_to_return_; }
+
+ private:
+ Status status_to_return_;
+};
+
+class DummyPassPreservesAll : public Pass {
+ public:
+ DummyPassPreservesAll(Status s) : Pass(), status_to_return_(s) {}
+
+ const char* name() const override { return "dummy-pass"; }
+ Status Process() override { return status_to_return_; }
+
+ Analysis GetPreservedAnalyses() override {
+ return Analysis(IRContext::kAnalysisEnd - 1);
+ }
+
+ private:
+ Status status_to_return_;
+};
+
+class DummyPassPreservesFirst : public Pass {
+ public:
+ DummyPassPreservesFirst(Status s) : Pass(), status_to_return_(s) {}
+
+ const char* name() const override { return "dummy-pass"; }
+ Status Process() override { return status_to_return_; }
+
+ Analysis GetPreservedAnalyses() override { return IRContext::kAnalysisBegin; }
+
+ private:
+ Status status_to_return_;
+};
+
+using IRContextTest = PassTest<::testing::Test>;
+
+TEST_F(IRContextTest, IndividualValidAfterBuild) {
+ std::unique_ptr<Module> module(new Module());
+ IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+ spvtools::MessageConsumer());
+
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ localContext.BuildInvalidAnalyses(i);
+ EXPECT_TRUE(localContext.AreAnalysesValid(i));
+ }
+}
+
+TEST_F(IRContextTest, AllValidAfterBuild) {
+ std::unique_ptr<Module> module = MakeUnique<Module>();
+ IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+ spvtools::MessageConsumer());
+
+ Analysis built_analyses = IRContext::kAnalysisNone;
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ localContext.BuildInvalidAnalyses(i);
+ built_analyses |= i;
+ }
+ EXPECT_TRUE(localContext.AreAnalysesValid(built_analyses));
+}
+
+TEST_F(IRContextTest, AllValidAfterPassNoChange) {
+ std::unique_ptr<Module> module = MakeUnique<Module>();
+ IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+ spvtools::MessageConsumer());
+
+ Analysis built_analyses = IRContext::kAnalysisNone;
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ localContext.BuildInvalidAnalyses(i);
+ built_analyses |= i;
+ }
+
+ DummyPassPreservesNothing pass(Pass::Status::SuccessWithoutChange);
+ Pass::Status s = pass.Run(&localContext);
+ EXPECT_EQ(s, Pass::Status::SuccessWithoutChange);
+ EXPECT_TRUE(localContext.AreAnalysesValid(built_analyses));
+}
+
+TEST_F(IRContextTest, NoneValidAfterPassWithChange) {
+ std::unique_ptr<Module> module = MakeUnique<Module>();
+ IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+ spvtools::MessageConsumer());
+
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ localContext.BuildInvalidAnalyses(i);
+ }
+
+ DummyPassPreservesNothing pass(Pass::Status::SuccessWithChange);
+ Pass::Status s = pass.Run(&localContext);
+ EXPECT_EQ(s, Pass::Status::SuccessWithChange);
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ EXPECT_FALSE(localContext.AreAnalysesValid(i));
+ }
+}
+
+TEST_F(IRContextTest, AllPreservedAfterPassWithChange) {
+ std::unique_ptr<Module> module = MakeUnique<Module>();
+ IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+ spvtools::MessageConsumer());
+
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ localContext.BuildInvalidAnalyses(i);
+ }
+
+ DummyPassPreservesAll pass(Pass::Status::SuccessWithChange);
+ Pass::Status s = pass.Run(&localContext);
+ EXPECT_EQ(s, Pass::Status::SuccessWithChange);
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ EXPECT_TRUE(localContext.AreAnalysesValid(i));
+ }
+}
+
+TEST_F(IRContextTest, PreserveFirstOnlyAfterPassWithChange) {
+ std::unique_ptr<Module> module = MakeUnique<Module>();
+ IRContext localContext(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+ spvtools::MessageConsumer());
+
+ for (Analysis i = IRContext::kAnalysisBegin; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ localContext.BuildInvalidAnalyses(i);
+ }
+
+ DummyPassPreservesFirst pass(Pass::Status::SuccessWithChange);
+ Pass::Status s = pass.Run(&localContext);
+ EXPECT_EQ(s, Pass::Status::SuccessWithChange);
+ EXPECT_TRUE(localContext.AreAnalysesValid(IRContext::kAnalysisBegin));
+ for (Analysis i = IRContext::kAnalysisBegin << 1; i < IRContext::kAnalysisEnd;
+ i <<= 1) {
+ EXPECT_FALSE(localContext.AreAnalysesValid(i));
+ }
+}
+
+TEST_F(IRContextTest, KillMemberName) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %3 "stuff"
+ OpMemberName %3 0 "refZ"
+ OpMemberDecorate %3 0 Offset 0
+ OpDecorate %3 Block
+ %4 = OpTypeFloat 32
+ %3 = OpTypeStruct %4
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %2 = OpFunction %5 None %6
+ %7 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build the decoration manager.
+ context->get_decoration_mgr();
+
+ // Delete the OpTypeStruct. Should delete the OpName, OpMemberName, and
+ // OpMemberDecorate associated with it.
+ context->KillDef(3);
+
+ // Make sure all of the name are removed.
+ for (auto& inst : context->debugs2()) {
+ EXPECT_EQ(inst.opcode(), SpvOpNop);
+ }
+
+ // Make sure all of the decorations are removed.
+ for (auto& inst : context->annotations()) {
+ EXPECT_EQ(inst.opcode(), SpvOpNop);
+ }
+}
+
+TEST_F(IRContextTest, KillGroupDecoration) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpDecorate %3 Restrict
+ %3 = OpDecorationGroup
+ OpGroupDecorate %3 %4 %5
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeStruct %6
+ %9 = OpTypeVoid
+ %10 = OpTypeFunction %9
+ %2 = OpFunction %9 None %10
+ %11 = OpLabel
+ %4 = OpVariable %7 Function
+ %5 = OpVariable %7 Function
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build the decoration manager.
+ context->get_decoration_mgr();
+
+ // Delete the second variable.
+ context->KillDef(5);
+
+ // The three decorations instructions should still be there. The first two
+ // should be the same, but the third should have %5 removed.
+
+ // Check the OpDecorate instruction
+ auto inst = context->annotation_begin();
+ EXPECT_EQ(inst->opcode(), SpvOpDecorate);
+ EXPECT_EQ(inst->GetSingleWordInOperand(0), 3);
+
+ // Check the OpDecorationGroup Instruction
+ ++inst;
+ EXPECT_EQ(inst->opcode(), SpvOpDecorationGroup);
+ EXPECT_EQ(inst->result_id(), 3);
+
+ // Check that %5 is no longer part of the group.
+ ++inst;
+ EXPECT_EQ(inst->opcode(), SpvOpGroupDecorate);
+ EXPECT_EQ(inst->NumInOperands(), 2);
+ EXPECT_EQ(inst->GetSingleWordInOperand(0), 3);
+ EXPECT_EQ(inst->GetSingleWordInOperand(1), 4);
+
+ // Check that we are at the end.
+ ++inst;
+ EXPECT_EQ(inst, context->annotation_end());
+}
+
+TEST_F(IRContextTest, TakeNextUniqueIdIncrementing) {
+ const uint32_t NUM_TESTS = 1000;
+ IRContext localContext(SPV_ENV_UNIVERSAL_1_2, nullptr);
+ for (uint32_t i = 1; i < NUM_TESTS; ++i)
+ EXPECT_EQ(i, localContext.TakeNextUniqueId());
+}
+
+TEST_F(IRContextTest, KillGroupDecorationWitNoDecorations) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpDecorationGroup
+ OpGroupDecorate %3 %4 %5
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeStruct %6
+ %9 = OpTypeVoid
+ %10 = OpTypeFunction %9
+ %2 = OpFunction %9 None %10
+ %11 = OpLabel
+ %4 = OpVariable %7 Function
+ %5 = OpVariable %7 Function
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build the decoration manager.
+ context->get_decoration_mgr();
+
+ // Delete the second variable.
+ context->KillDef(5);
+
+ // The two decoration instructions should still be there. The first one
+ // should be the same, but the second should have %5 removed.
+
+ // Check the OpDecorationGroup Instruction
+ auto inst = context->annotation_begin();
+ EXPECT_EQ(inst->opcode(), SpvOpDecorationGroup);
+ EXPECT_EQ(inst->result_id(), 3);
+
+ // Check that %5 is no longer part of the group.
+ ++inst;
+ EXPECT_EQ(inst->opcode(), SpvOpGroupDecorate);
+ EXPECT_EQ(inst->NumInOperands(), 2);
+ EXPECT_EQ(inst->GetSingleWordInOperand(0), 3);
+ EXPECT_EQ(inst->GetSingleWordInOperand(1), 4);
+
+ // Check that we are at the end.
+ ++inst;
+ EXPECT_EQ(inst, context->annotation_end());
+}
+
+TEST_F(IRContextTest, KillDecorationGroup) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpDecorationGroup
+ OpGroupDecorate %3 %4 %5
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeStruct %6
+ %9 = OpTypeVoid
+ %10 = OpTypeFunction %9
+ %2 = OpFunction %9 None %10
+ %11 = OpLabel
+ %4 = OpVariable %7 Function
+ %5 = OpVariable %7 Function
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+ // Build the decoration manager.
+ context->get_decoration_mgr();
+
+ // Delete the second variable.
+ context->KillDef(3);
+
+ // Check the OpDecorationGroup Instruction is still there.
+ EXPECT_TRUE(context->annotations().empty());
+}
+
+TEST_F(IRContextTest, BasicVisitFromEntryPoint) {
+ // Make sure we visit the entry point, and the function it calls.
+ // Do not visit Dead or Exported.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main"
+ OpName %10 "main"
+ OpName %Dead "Dead"
+ OpName %11 "Constant"
+ OpName %ExportedFunc "ExportedFunc"
+ OpDecorate %ExportedFunc LinkageAttributes "ExportedFunc" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ %15 = OpFunctionCall %void %11
+ %16 = OpFunctionCall %void %11
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %void None %6
+ %18 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %6
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+%ExportedFunc = OpFunction %void None %7
+ %20 = OpLabel
+ %21 = OpFunctionCall %void %11
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<IRContext> localContext =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ std::vector<uint32_t> processed;
+ Pass::ProcessFunction mark_visited = [&processed](Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ localContext->ProcessEntryPointCallTree(mark_visited);
+ EXPECT_THAT(processed, UnorderedElementsAre(10, 11));
+}
+
+TEST_F(IRContextTest, BasicVisitReachable) {
+ // Make sure we visit the entry point, exported function, and the function
+ // they call. Do not visit Dead.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main"
+ OpName %10 "main"
+ OpName %Dead "Dead"
+ OpName %11 "Constant"
+ OpName %12 "ExportedFunc"
+ OpName %13 "Constant2"
+ OpDecorate %12 LinkageAttributes "ExportedFunc" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ %15 = OpFunctionCall %void %11
+ %16 = OpFunctionCall %void %11
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %void None %6
+ %18 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %6
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %void None %6
+ %20 = OpLabel
+ %21 = OpFunctionCall %void %13
+ OpReturn
+ OpFunctionEnd
+ %13 = OpFunction %void None %6
+ %22 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<IRContext> localContext =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ std::vector<uint32_t> processed;
+ Pass::ProcessFunction mark_visited = [&processed](Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ localContext->ProcessReachableCallTree(mark_visited);
+ EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12, 13));
+}
+
+TEST_F(IRContextTest, BasicVisitOnlyOnce) {
+ // Make sure we visit %12 only once, even if it is called from two different
+ // functions.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main"
+ OpName %10 "main"
+ OpName %Dead "Dead"
+ OpName %11 "Constant"
+ OpName %12 "ExportedFunc"
+ OpDecorate %12 LinkageAttributes "ExportedFunc" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ %15 = OpFunctionCall %void %11
+ %16 = OpFunctionCall %void %12
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %void None %6
+ %18 = OpLabel
+ %19 = OpFunctionCall %void %12
+ OpReturn
+ OpFunctionEnd
+ %Dead = OpFunction %void None %6
+ %20 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %void None %6
+ %21 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<IRContext> localContext =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ std::vector<uint32_t> processed;
+ Pass::ProcessFunction mark_visited = [&processed](Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ localContext->ProcessReachableCallTree(mark_visited);
+ EXPECT_THAT(processed, UnorderedElementsAre(10, 11, 12));
+}
+
+TEST_F(IRContextTest, BasicDontVisitExportedVariable) {
+ // Make sure we only visit functions and not exported variables.
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %10 "main"
+ OpExecutionMode %10 OriginUpperLeft
+ OpSource GLSL 150
+ OpName %10 "main"
+ OpName %12 "export_var"
+ OpDecorate %12 LinkageAttributes "export_var" Export
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %float_1 = OpConstant %float 1
+ %12 = OpVariable %float Output
+ %10 = OpFunction %void None %6
+ %14 = OpLabel
+ OpStore %12 %float_1
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+
+ std::unique_ptr<IRContext> localContext =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, localContext) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ std::vector<uint32_t> processed;
+ Pass::ProcessFunction mark_visited = [&processed](Function* fp) {
+ processed.push_back(fp->result_id());
+ return false;
+ };
+ localContext->ProcessReachableCallTree(mark_visited);
+ EXPECT_THAT(processed, UnorderedElementsAre(10));
+}
+
+TEST_F(IRContextTest, IdBoundTestAtLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+ EXPECT_EQ(current_bound, context->module()->id_bound());
+ next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST_F(IRContextTest, IdBoundTestBelowLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound + 100);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, current_bound);
+ EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+ next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, current_bound + 1);
+}
+
+TEST_F(IRContextTest, IdBoundTestNearLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound + 1);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, current_bound);
+ EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+ next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST_F(IRContextTest, IdBoundTestUIntMax) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4294967294 = OpLabel ; ID is UINT_MAX-1
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+
+ // Expecting |BuildModule| to preserve the numeric ids.
+ EXPECT_EQ(current_bound, std::numeric_limits<uint32_t>::max());
+
+ context->set_max_id_bound(current_bound);
+ uint32_t next_id_bound = context->TakeNextId();
+ EXPECT_EQ(next_id_bound, 0);
+ EXPECT_EQ(current_bound, context->module()->id_bound());
+}
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/ir_loader_test.cpp b/test/opt/ir_loader_test.cpp
new file mode 100644
index 0000000..ac5c520
--- /dev/null
+++ b/test/opt/ir_loader_test.cpp
@@ -0,0 +1,451 @@
+// Copyright (c) 2016 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 <algorithm>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+void DoRoundTripCheck(const std::string& text) {
+ SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ ASSERT_NE(nullptr, context) << "Failed to assemble\n" << text;
+
+ std::vector<uint32_t> binary;
+ context->module()->ToBinary(&binary, /* skip_nop = */ false);
+
+ std::string disassembled_text;
+ EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
+ EXPECT_EQ(text, disassembled_text);
+}
+
+TEST(IrBuilder, RoundTrip) {
+ // #version 310 es
+ // int add(int a, int b) { return a + b; }
+ // void main() { add(1, 2); }
+ DoRoundTripCheck(
+ // clang-format off
+ "OpCapability Shader\n"
+ "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "OpEntryPoint Vertex %main \"main\"\n"
+ "OpSource ESSL 310\n"
+ "OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"\n"
+ "OpSourceExtension \"GL_GOOGLE_include_directive\"\n"
+ "OpName %main \"main\"\n"
+ "OpName %add_i1_i1_ \"add(i1;i1;\"\n"
+ "OpName %a \"a\"\n"
+ "OpName %b \"b\"\n"
+ "OpName %param \"param\"\n"
+ "OpName %param_0 \"param\"\n"
+ "%void = OpTypeVoid\n"
+ "%9 = OpTypeFunction %void\n"
+ "%int = OpTypeInt 32 1\n"
+ "%_ptr_Function_int = OpTypePointer Function %int\n"
+ "%12 = OpTypeFunction %int %_ptr_Function_int %_ptr_Function_int\n"
+ "%int_1 = OpConstant %int 1\n"
+ "%int_2 = OpConstant %int 2\n"
+ "%main = OpFunction %void None %9\n"
+ "%15 = OpLabel\n"
+ "%param = OpVariable %_ptr_Function_int Function\n"
+ "%param_0 = OpVariable %_ptr_Function_int Function\n"
+ "OpStore %param %int_1\n"
+ "OpStore %param_0 %int_2\n"
+ "%16 = OpFunctionCall %int %add_i1_i1_ %param %param_0\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n"
+ "%add_i1_i1_ = OpFunction %int None %12\n"
+ "%a = OpFunctionParameter %_ptr_Function_int\n"
+ "%b = OpFunctionParameter %_ptr_Function_int\n"
+ "%17 = OpLabel\n"
+ "%18 = OpLoad %int %a\n"
+ "%19 = OpLoad %int %b\n"
+ "%20 = OpIAdd %int %18 %19\n"
+ "OpReturnValue %20\n"
+ "OpFunctionEnd\n");
+ // clang-format on
+}
+
+TEST(IrBuilder, RoundTripIncompleteBasicBlock) {
+ DoRoundTripCheck(
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "OpNop\n");
+}
+
+TEST(IrBuilder, RoundTripIncompleteFunction) {
+ DoRoundTripCheck("%2 = OpFunction %1 None %3\n");
+}
+
+TEST(IrBuilder, KeepLineDebugInfo) {
+ // #version 310 es
+ // void main() {}
+ DoRoundTripCheck(
+ // clang-format off
+ "OpCapability Shader\n"
+ "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "OpEntryPoint Vertex %main \"main\"\n"
+ "%3 = OpString \"minimal.vert\"\n"
+ "OpSource ESSL 310\n"
+ "OpName %main \"main\"\n"
+ "OpLine %3 10 10\n"
+ "%void = OpTypeVoid\n"
+ "OpLine %3 100 100\n"
+ "%5 = OpTypeFunction %void\n"
+ "%main = OpFunction %void None %5\n"
+ "OpLine %3 1 1\n"
+ "OpNoLine\n"
+ "OpLine %3 2 2\n"
+ "OpLine %3 3 3\n"
+ "%6 = OpLabel\n"
+ "OpLine %3 4 4\n"
+ "OpNoLine\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n");
+ // clang-format on
+}
+
+TEST(IrBuilder, LocalGlobalVariables) {
+ // #version 310 es
+ //
+ // float gv1 = 10.;
+ // float gv2 = 100.;
+ //
+ // float f() {
+ // float lv1 = gv1 + gv2;
+ // float lv2 = gv1 * gv2;
+ // return lv1 / lv2;
+ // }
+ //
+ // void main() {
+ // float lv1 = gv1 - gv2;
+ // }
+ DoRoundTripCheck(
+ // clang-format off
+ "OpCapability Shader\n"
+ "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "OpEntryPoint Vertex %main \"main\"\n"
+ "OpSource ESSL 310\n"
+ "OpName %main \"main\"\n"
+ "OpName %f_ \"f(\"\n"
+ "OpName %gv1 \"gv1\"\n"
+ "OpName %gv2 \"gv2\"\n"
+ "OpName %lv1 \"lv1\"\n"
+ "OpName %lv2 \"lv2\"\n"
+ "OpName %lv1_0 \"lv1\"\n"
+ "%void = OpTypeVoid\n"
+ "%10 = OpTypeFunction %void\n"
+ "%float = OpTypeFloat 32\n"
+ "%12 = OpTypeFunction %float\n"
+ "%_ptr_Private_float = OpTypePointer Private %float\n"
+ "%gv1 = OpVariable %_ptr_Private_float Private\n"
+ "%float_10 = OpConstant %float 10\n"
+ "%gv2 = OpVariable %_ptr_Private_float Private\n"
+ "%float_100 = OpConstant %float 100\n"
+ "%_ptr_Function_float = OpTypePointer Function %float\n"
+ "%main = OpFunction %void None %10\n"
+ "%17 = OpLabel\n"
+ "%lv1_0 = OpVariable %_ptr_Function_float Function\n"
+ "OpStore %gv1 %float_10\n"
+ "OpStore %gv2 %float_100\n"
+ "%18 = OpLoad %float %gv1\n"
+ "%19 = OpLoad %float %gv2\n"
+ "%20 = OpFSub %float %18 %19\n"
+ "OpStore %lv1_0 %20\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n"
+ "%f_ = OpFunction %float None %12\n"
+ "%21 = OpLabel\n"
+ "%lv1 = OpVariable %_ptr_Function_float Function\n"
+ "%lv2 = OpVariable %_ptr_Function_float Function\n"
+ "%22 = OpLoad %float %gv1\n"
+ "%23 = OpLoad %float %gv2\n"
+ "%24 = OpFAdd %float %22 %23\n"
+ "OpStore %lv1 %24\n"
+ "%25 = OpLoad %float %gv1\n"
+ "%26 = OpLoad %float %gv2\n"
+ "%27 = OpFMul %float %25 %26\n"
+ "OpStore %lv2 %27\n"
+ "%28 = OpLoad %float %lv1\n"
+ "%29 = OpLoad %float %lv2\n"
+ "%30 = OpFDiv %float %28 %29\n"
+ "OpReturnValue %30\n"
+ "OpFunctionEnd\n");
+ // clang-format on
+}
+
+TEST(IrBuilder, OpUndefOutsideFunction) {
+ // #version 310 es
+ // void main() {}
+ const std::string text =
+ // clang-format off
+ "OpMemoryModel Logical GLSL450\n"
+ "%int = OpTypeInt 32 1\n"
+ "%uint = OpTypeInt 32 0\n"
+ "%float = OpTypeFloat 32\n"
+ "%4 = OpUndef %int\n"
+ "%int_10 = OpConstant %int 10\n"
+ "%6 = OpUndef %uint\n"
+ "%bool = OpTypeBool\n"
+ "%8 = OpUndef %float\n"
+ "%double = OpTypeFloat 64\n";
+ // clang-format on
+
+ SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ ASSERT_NE(nullptr, context);
+
+ const auto opundef_count = std::count_if(
+ context->module()->types_values_begin(),
+ context->module()->types_values_end(),
+ [](const Instruction& inst) { return inst.opcode() == SpvOpUndef; });
+ EXPECT_EQ(3, opundef_count);
+
+ std::vector<uint32_t> binary;
+ context->module()->ToBinary(&binary, /* skip_nop = */ false);
+
+ std::string disassembled_text;
+ EXPECT_TRUE(t.Disassemble(binary, &disassembled_text));
+ EXPECT_EQ(text, disassembled_text);
+}
+
+TEST(IrBuilder, OpUndefInBasicBlock) {
+ DoRoundTripCheck(
+ // clang-format off
+ "OpMemoryModel Logical GLSL450\n"
+ "OpName %main \"main\"\n"
+ "%void = OpTypeVoid\n"
+ "%uint = OpTypeInt 32 0\n"
+ "%double = OpTypeFloat 64\n"
+ "%5 = OpTypeFunction %void\n"
+ "%main = OpFunction %void None %5\n"
+ "%6 = OpLabel\n"
+ "%7 = OpUndef %uint\n"
+ "%8 = OpUndef %double\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n");
+ // clang-format on
+}
+
+TEST(IrBuilder, KeepLineDebugInfoBeforeType) {
+ DoRoundTripCheck(
+ // clang-format off
+ "OpCapability Shader\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "%1 = OpString \"minimal.vert\"\n"
+ "OpLine %1 1 1\n"
+ "OpNoLine\n"
+ "%void = OpTypeVoid\n"
+ "OpLine %1 2 2\n"
+ "%3 = OpTypeFunction %void\n");
+ // clang-format on
+}
+
+TEST(IrBuilder, KeepLineDebugInfoBeforeLabel) {
+ DoRoundTripCheck(
+ // clang-format off
+ "OpCapability Shader\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "%1 = OpString \"minimal.vert\"\n"
+ "%void = OpTypeVoid\n"
+ "%3 = OpTypeFunction %void\n"
+ "%4 = OpFunction %void None %3\n"
+ "%5 = OpLabel\n"
+ "OpBranch %6\n"
+ "OpLine %1 1 1\n"
+ "OpLine %1 2 2\n"
+ "%6 = OpLabel\n"
+ "OpBranch %7\n"
+ "OpLine %1 100 100\n"
+ "%7 = OpLabel\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n");
+ // clang-format on
+}
+
+TEST(IrBuilder, KeepLineDebugInfoBeforeFunctionEnd) {
+ DoRoundTripCheck(
+ // clang-format off
+ "OpCapability Shader\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "%1 = OpString \"minimal.vert\"\n"
+ "%void = OpTypeVoid\n"
+ "%3 = OpTypeFunction %void\n"
+ "%4 = OpFunction %void None %3\n"
+ "OpLine %1 1 1\n"
+ "OpLine %1 2 2\n"
+ "OpFunctionEnd\n");
+ // clang-format on
+}
+
+TEST(IrBuilder, KeepModuleProcessedInRightPlace) {
+ DoRoundTripCheck(
+ // clang-format off
+ "OpCapability Shader\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "%1 = OpString \"minimal.vert\"\n"
+ "OpName %void \"void\"\n"
+ "OpModuleProcessed \"Made it faster\"\n"
+ "OpModuleProcessed \".. and smaller\"\n"
+ "%void = OpTypeVoid\n");
+ // clang-format on
+}
+
+// Checks the given |error_message| is reported when trying to build a module
+// from the given |assembly|.
+void DoErrorMessageCheck(const std::string& assembly,
+ const std::string& error_message, uint32_t line_num) {
+ auto consumer = [error_message, line_num](spv_message_level_t, const char*,
+ const spv_position_t& position,
+ const char* m) {
+ EXPECT_EQ(error_message, m);
+ EXPECT_EQ(line_num, position.line);
+ };
+
+ SpirvTools t(SPV_ENV_UNIVERSAL_1_1);
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, std::move(consumer), assembly);
+ EXPECT_EQ(nullptr, context);
+}
+
+TEST(IrBuilder, FunctionInsideFunction) {
+ DoErrorMessageCheck("%2 = OpFunction %1 None %3\n%5 = OpFunction %4 None %6",
+ "function inside function", 2);
+}
+
+TEST(IrBuilder, MismatchOpFunctionEnd) {
+ DoErrorMessageCheck("OpFunctionEnd",
+ "OpFunctionEnd without corresponding OpFunction", 1);
+}
+
+TEST(IrBuilder, OpFunctionEndInsideBasicBlock) {
+ DoErrorMessageCheck(
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "OpFunctionEnd",
+ "OpFunctionEnd inside basic block", 3);
+}
+
+TEST(IrBuilder, BasicBlockOutsideFunction) {
+ DoErrorMessageCheck("OpCapability Shader\n%1 = OpLabel",
+ "OpLabel outside function", 2);
+}
+
+TEST(IrBuilder, OpLabelInsideBasicBlock) {
+ DoErrorMessageCheck(
+ "%2 = OpFunction %1 None %3\n"
+ "%4 = OpLabel\n"
+ "%5 = OpLabel",
+ "OpLabel inside basic block", 3);
+}
+
+TEST(IrBuilder, TerminatorOutsideFunction) {
+ DoErrorMessageCheck("OpReturn", "terminator instruction outside function", 1);
+}
+
+TEST(IrBuilder, TerminatorOutsideBasicBlock) {
+ DoErrorMessageCheck("%2 = OpFunction %1 None %3\nOpReturn",
+ "terminator instruction outside basic block", 2);
+}
+
+TEST(IrBuilder, NotAllowedInstAppearingInFunction) {
+ DoErrorMessageCheck("%2 = OpFunction %1 None %3\n%5 = OpVariable %4 Function",
+ "Non-OpFunctionParameter (opcode: 59) found inside "
+ "function but outside basic block",
+ 2);
+}
+
+TEST(IrBuilder, UniqueIds) {
+ const std::string text =
+ // clang-format off
+ "OpCapability Shader\n"
+ "%1 = OpExtInstImport \"GLSL.std.450\"\n"
+ "OpMemoryModel Logical GLSL450\n"
+ "OpEntryPoint Vertex %main \"main\"\n"
+ "OpSource ESSL 310\n"
+ "OpName %main \"main\"\n"
+ "OpName %f_ \"f(\"\n"
+ "OpName %gv1 \"gv1\"\n"
+ "OpName %gv2 \"gv2\"\n"
+ "OpName %lv1 \"lv1\"\n"
+ "OpName %lv2 \"lv2\"\n"
+ "OpName %lv1_0 \"lv1\"\n"
+ "%void = OpTypeVoid\n"
+ "%10 = OpTypeFunction %void\n"
+ "%float = OpTypeFloat 32\n"
+ "%12 = OpTypeFunction %float\n"
+ "%_ptr_Private_float = OpTypePointer Private %float\n"
+ "%gv1 = OpVariable %_ptr_Private_float Private\n"
+ "%float_10 = OpConstant %float 10\n"
+ "%gv2 = OpVariable %_ptr_Private_float Private\n"
+ "%float_100 = OpConstant %float 100\n"
+ "%_ptr_Function_float = OpTypePointer Function %float\n"
+ "%main = OpFunction %void None %10\n"
+ "%17 = OpLabel\n"
+ "%lv1_0 = OpVariable %_ptr_Function_float Function\n"
+ "OpStore %gv1 %float_10\n"
+ "OpStore %gv2 %float_100\n"
+ "%18 = OpLoad %float %gv1\n"
+ "%19 = OpLoad %float %gv2\n"
+ "%20 = OpFSub %float %18 %19\n"
+ "OpStore %lv1_0 %20\n"
+ "OpReturn\n"
+ "OpFunctionEnd\n"
+ "%f_ = OpFunction %float None %12\n"
+ "%21 = OpLabel\n"
+ "%lv1 = OpVariable %_ptr_Function_float Function\n"
+ "%lv2 = OpVariable %_ptr_Function_float Function\n"
+ "%22 = OpLoad %float %gv1\n"
+ "%23 = OpLoad %float %gv2\n"
+ "%24 = OpFAdd %float %22 %23\n"
+ "OpStore %lv1 %24\n"
+ "%25 = OpLoad %float %gv1\n"
+ "%26 = OpLoad %float %gv2\n"
+ "%27 = OpFMul %float %25 %26\n"
+ "OpStore %lv2 %27\n"
+ "%28 = OpLoad %float %lv1\n"
+ "%29 = OpLoad %float %lv2\n"
+ "%30 = OpFDiv %float %28 %29\n"
+ "OpReturnValue %30\n"
+ "OpFunctionEnd\n";
+ // clang-format on
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ ASSERT_NE(nullptr, context);
+
+ std::unordered_set<uint32_t> ids;
+ context->module()->ForEachInst([&ids](const Instruction* inst) {
+ EXPECT_TRUE(ids.insert(inst->unique_id()).second);
+ });
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/iterator_test.cpp b/test/opt/iterator_test.cpp
new file mode 100644
index 0000000..d61bc1a
--- /dev/null
+++ b/test/opt/iterator_test.cpp
@@ -0,0 +1,267 @@
+// Copyright (c) 2016 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 <memory>
+#include <vector>
+
+#include "gmock/gmock.h"
+
+#include "source/opt/iterator.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::ContainerEq;
+
+TEST(Iterator, IncrementDeref) {
+ const int count = 100;
+ std::vector<std::unique_ptr<int>> data;
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ }
+
+ UptrVectorIterator<int> it(&data, data.begin());
+ UptrVectorIterator<int> end(&data, data.end());
+
+ EXPECT_EQ(*data[0], *it);
+ for (int i = 1; i < count; ++i) {
+ EXPECT_NE(end, it);
+ EXPECT_EQ(*data[i], *(++it));
+ }
+ EXPECT_EQ(end, ++it);
+}
+
+TEST(Iterator, DecrementDeref) {
+ const int count = 100;
+ std::vector<std::unique_ptr<int>> data;
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ }
+
+ UptrVectorIterator<int> begin(&data, data.begin());
+ UptrVectorIterator<int> it(&data, data.end());
+
+ for (int i = count - 1; i >= 0; --i) {
+ EXPECT_NE(begin, it);
+ EXPECT_EQ(*data[i], *(--it));
+ }
+ EXPECT_EQ(begin, it);
+}
+
+TEST(Iterator, PostIncrementDeref) {
+ const int count = 100;
+ std::vector<std::unique_ptr<int>> data;
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ }
+
+ UptrVectorIterator<int> it(&data, data.begin());
+ UptrVectorIterator<int> end(&data, data.end());
+
+ for (int i = 0; i < count; ++i) {
+ EXPECT_NE(end, it);
+ EXPECT_EQ(*data[i], *(it++));
+ }
+ EXPECT_EQ(end, it);
+}
+
+TEST(Iterator, PostDecrementDeref) {
+ const int count = 100;
+ std::vector<std::unique_ptr<int>> data;
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ }
+
+ UptrVectorIterator<int> begin(&data, data.begin());
+ UptrVectorIterator<int> end(&data, data.end());
+ UptrVectorIterator<int> it(&data, data.end());
+
+ EXPECT_EQ(end, it--);
+ for (int i = count - 1; i >= 1; --i) {
+ EXPECT_EQ(*data[i], *(it--));
+ }
+ // Decrementing .begin() is undefined behavior.
+ EXPECT_EQ(*data[0], *it);
+}
+
+TEST(Iterator, Access) {
+ const int count = 100;
+ std::vector<std::unique_ptr<int>> data;
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ }
+
+ UptrVectorIterator<int> it(&data, data.begin());
+
+ for (int i = 0; i < count; ++i) EXPECT_EQ(*data[i], it[i]);
+}
+
+TEST(Iterator, Comparison) {
+ const int count = 100;
+ std::vector<std::unique_ptr<int>> data;
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ }
+
+ UptrVectorIterator<int> it(&data, data.begin());
+ UptrVectorIterator<int> end(&data, data.end());
+
+ for (int i = 0; i < count; ++i, ++it) EXPECT_TRUE(it < end);
+ EXPECT_EQ(end, it);
+}
+
+TEST(Iterator, InsertBeginEnd) {
+ const int count = 100;
+
+ std::vector<std::unique_ptr<int>> data;
+ std::vector<int> expected;
+ std::vector<int> actual;
+
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ expected.push_back(i);
+ }
+
+ // Insert at the beginning
+ expected.insert(expected.begin(), -100);
+ UptrVectorIterator<int> begin(&data, data.begin());
+ auto insert_point = begin.InsertBefore(MakeUnique<int>(-100));
+ for (int i = 0; i < count + 1; ++i) {
+ actual.push_back(*(insert_point++));
+ }
+ EXPECT_THAT(actual, ContainerEq(expected));
+
+ // Insert at the end
+ expected.push_back(-42);
+ expected.push_back(-36);
+ expected.push_back(-77);
+ UptrVectorIterator<int> end(&data, data.end());
+ end = end.InsertBefore(MakeUnique<int>(-77));
+ end = end.InsertBefore(MakeUnique<int>(-36));
+ end = end.InsertBefore(MakeUnique<int>(-42));
+
+ actual.clear();
+ begin = UptrVectorIterator<int>(&data, data.begin());
+ for (int i = 0; i < count + 4; ++i) {
+ actual.push_back(*(begin++));
+ }
+ EXPECT_THAT(actual, ContainerEq(expected));
+}
+
+TEST(Iterator, InsertMiddle) {
+ const int count = 100;
+
+ std::vector<std::unique_ptr<int>> data;
+ std::vector<int> expected;
+ std::vector<int> actual;
+
+ for (int i = 0; i < count; ++i) {
+ data.emplace_back(new int(i));
+ expected.push_back(i);
+ }
+
+ const int insert_pos = 42;
+ expected.insert(expected.begin() + insert_pos, -100);
+ expected.insert(expected.begin() + insert_pos, -42);
+
+ UptrVectorIterator<int> it(&data, data.begin());
+ for (int i = 0; i < insert_pos; ++i) ++it;
+ it = it.InsertBefore(MakeUnique<int>(-100));
+ it = it.InsertBefore(MakeUnique<int>(-42));
+ auto begin = UptrVectorIterator<int>(&data, data.begin());
+ for (int i = 0; i < count + 2; ++i) {
+ actual.push_back(*(begin++));
+ }
+ EXPECT_THAT(actual, ContainerEq(expected));
+}
+
+TEST(IteratorRange, Interface) {
+ const uint32_t count = 100;
+
+ std::vector<std::unique_ptr<uint32_t>> data;
+
+ for (uint32_t i = 0; i < count; ++i) {
+ data.emplace_back(new uint32_t(i));
+ }
+
+ auto b = UptrVectorIterator<uint32_t>(&data, data.begin());
+ auto e = UptrVectorIterator<uint32_t>(&data, data.end());
+ auto range = IteratorRange<decltype(b)>(b, e);
+
+ EXPECT_EQ(b, range.begin());
+ EXPECT_EQ(e, range.end());
+ EXPECT_FALSE(range.empty());
+ EXPECT_EQ(count, range.size());
+ EXPECT_EQ(0u, *range.begin());
+ EXPECT_EQ(99u, *(--range.end()));
+
+ // IteratorRange itself is immutable.
+ ++b, --e;
+ EXPECT_EQ(count, range.size());
+ ++range.begin(), --range.end();
+ EXPECT_EQ(count, range.size());
+}
+
+TEST(Iterator, FilterIterator) {
+ struct Placeholder {
+ int val;
+ };
+ std::vector<Placeholder> data = {{1}, {2}, {3}, {4}, {5},
+ {6}, {7}, {8}, {9}, {10}};
+
+ // Predicate to only consider odd values.
+ struct Predicate {
+ bool operator()(const Placeholder& data) { return data.val % 2; }
+ };
+ Predicate pred;
+
+ auto filter_range = MakeFilterIteratorRange(data.begin(), data.end(), pred);
+
+ EXPECT_EQ(filter_range.begin().Get(), data.begin());
+ EXPECT_EQ(filter_range.end(), filter_range.begin().GetEnd());
+
+ for (Placeholder& data : filter_range) {
+ EXPECT_EQ(data.val % 2, 1);
+ }
+
+ for (auto it = filter_range.begin(); it != filter_range.end(); it++) {
+ EXPECT_EQ(it->val % 2, 1);
+ EXPECT_EQ((*it).val % 2, 1);
+ }
+
+ for (auto it = filter_range.begin(); it != filter_range.end(); ++it) {
+ EXPECT_EQ(it->val % 2, 1);
+ EXPECT_EQ((*it).val % 2, 1);
+ }
+
+ EXPECT_EQ(MakeFilterIterator(data.begin(), data.end(), pred).Get(),
+ data.begin());
+ EXPECT_EQ(MakeFilterIterator(data.end(), data.end(), pred).Get(), data.end());
+ EXPECT_EQ(MakeFilterIterator(data.begin(), data.end(), pred).GetEnd(),
+ MakeFilterIterator(data.end(), data.end(), pred));
+ EXPECT_NE(MakeFilterIterator(data.begin(), data.end(), pred),
+ MakeFilterIterator(data.end(), data.end(), pred));
+
+ // Empty range: no values satisfies the predicate.
+ auto empty_range = MakeFilterIteratorRange(
+ data.begin(), data.end(),
+ [](const Placeholder& data) { return data.val > 10; });
+ EXPECT_EQ(empty_range.begin(), empty_range.end());
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/line_debug_info_test.cpp b/test/opt/line_debug_info_test.cpp
new file mode 100644
index 0000000..6a20a01
--- /dev/null
+++ b/test/opt/line_debug_info_test.cpp
@@ -0,0 +1,113 @@
+// Copyright (c) 2016 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 "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+// A pass turning all none debug line instructions into Nop.
+class NopifyPass : public Pass {
+ public:
+ const char* name() const override { return "NopifyPass"; }
+ Status Process() override {
+ bool modified = false;
+ context()->module()->ForEachInst(
+ [&modified](Instruction* inst) {
+ inst->ToNop();
+ modified = true;
+ },
+ /* run_on_debug_line_insts = */ false);
+ return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+ }
+};
+
+using PassTestForLineDebugInfo = PassTest<::testing::Test>;
+
+// This test's purpose to show our implementation choice: line debug info is
+// preserved even if the following instruction is killed. It serves as a guard
+// of potential behavior changes.
+TEST_F(PassTestForLineDebugInfo, KeepLineDebugInfo) {
+ // clang-format off
+ const char* text =
+ "OpCapability Shader "
+ "%1 = OpExtInstImport \"GLSL.std.450\" "
+ "OpMemoryModel Logical GLSL450 "
+ "OpEntryPoint Vertex %2 \"main\" "
+ "%3 = OpString \"minimal.vert\" "
+ "OpNoLine "
+ "OpLine %3 10 10 "
+ "%void = OpTypeVoid "
+ "OpLine %3 100 100 "
+ "%5 = OpTypeFunction %void "
+ "%2 = OpFunction %void None %5 "
+ "OpLine %3 1 1 "
+ "OpNoLine "
+ "OpLine %3 2 2 "
+ "OpLine %3 3 3 "
+ "%6 = OpLabel "
+ "OpLine %3 4 4 "
+ "OpNoLine "
+ "OpReturn "
+ "OpLine %3 4 4 "
+ "OpNoLine "
+ "OpFunctionEnd ";
+ // clang-format on
+
+ const char* result_keep_nop =
+ "OpNop\n"
+ "OpNop\n"
+ "OpNop\n"
+ "OpNop\n"
+ "OpNop\n"
+ "OpNoLine\n"
+ "OpLine %3 10 10\n"
+ "OpNop\n"
+ "OpLine %3 100 100\n"
+ "OpNop\n"
+ "OpNop\n"
+ "OpLine %3 1 1\n"
+ "OpNoLine\n"
+ "OpLine %3 2 2\n"
+ "OpLine %3 3 3\n"
+ "OpNop\n"
+ "OpLine %3 4 4\n"
+ "OpNoLine\n"
+ "OpNop\n"
+ "OpLine %3 4 4\n"
+ "OpNoLine\n"
+ "OpNop\n";
+ SinglePassRunAndCheck<NopifyPass>(text, result_keep_nop,
+ /* skip_nop = */ false);
+ const char* result_skip_nop =
+ "OpNoLine\n"
+ "OpLine %3 10 10\n"
+ "OpLine %3 100 100\n"
+ "OpLine %3 1 1\n"
+ "OpNoLine\n"
+ "OpLine %3 2 2\n"
+ "OpLine %3 3 3\n"
+ "OpLine %3 4 4\n"
+ "OpNoLine\n"
+ "OpLine %3 4 4\n"
+ "OpNoLine\n";
+ SinglePassRunAndCheck<NopifyPass>(text, result_skip_nop,
+ /* skip_nop = */ true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/local_access_chain_convert_test.cpp b/test/opt/local_access_chain_convert_test.cpp
new file mode 100644
index 0000000..154824b
--- /dev/null
+++ b/test/opt/local_access_chain_convert_test.cpp
@@ -0,0 +1,714 @@
+// 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 LocalAccessChainConvertTest = PassTest<::testing::Test>;
+
+TEST_F(LocalAccessChainConvertTest, StructOfVecsOfFloatConverted) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // gl_FragColor = s0.v1;
+ // }
+
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: [[ld1:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex1:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[ld1]] 1
+; CHECK: OpStore %s0 [[ex1]]
+; CHECK: [[ld2:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1
+; CHECK: OpStore %gl_FragColor [[ex2]]
+%main = OpFunction %void None %8
+%17 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpStore %19 %18
+%20 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+%21 = OpLoad %v4float %20
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+ true);
+}
+
+TEST_F(LocalAccessChainConvertTest, InBoundsAccessChainsConverted) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // gl_FragColor = s0.v1;
+ // }
+
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: [[ld1:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex1:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[ld1]] 1
+; CHECK: OpStore %s0 [[ex1]]
+; CHECK: [[ld2:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1
+; CHECK: OpStore %gl_FragColor [[ex2]]
+%main = OpFunction %void None %8
+%17 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+%19 = OpInBoundsAccessChain %_ptr_Function_v4float %s0 %int_1
+OpStore %19 %18
+%20 = OpInBoundsAccessChain %_ptr_Function_v4float %s0 %int_1
+%21 = OpLoad %v4float %20
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+ true);
+}
+
+TEST_F(LocalAccessChainConvertTest, TwoUsesofSingleChainConverted) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // gl_FragColor = s0.v1;
+ // }
+
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: [[ld1:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex1:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[ld1]] 1
+; CHECK: OpStore %s0 [[ex1]]
+; CHECK: [[ld2:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1
+; CHECK: OpStore %gl_FragColor [[ex2]]
+%main = OpFunction %void None %8
+%17 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpStore %19 %18
+%20 = OpLoad %v4float %19
+OpStore %gl_FragColor %20
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+ true);
+}
+
+TEST_F(LocalAccessChainConvertTest, OpaqueConverted) {
+ // SPIR-V not representable in GLSL; not generatable from HLSL
+ // at the moment
+
+ const std::string predefs =
+ R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %outColor %texCoords
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpMemberName %S_t 2 "smp"
+OpName %foo_struct_S_t_vf2_vf21_ "foo(struct-S_t-vf2-vf21;"
+OpName %s "s"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %s0 "s0"
+OpName %texCoords "texCoords"
+OpName %param "param"
+OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%18 = OpTypeSampledImage %17
+%S_t = OpTypeStruct %v2float %v2float %18
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%20 = OpTypeFunction %void %_ptr_Function_S_t
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Function_18 = OpTypePointer Function %18
+%sampler15 = OpVariable %_ptr_UniformConstant_18 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string before =
+ R"(
+; CHECK: [[l1:%\w+]] = OpLoad %S_t %param
+; CHECK: [[e1:%\w+]] = OpCompositeExtract {{%\w+}} [[l1]] 2
+; CHECK: [[l2:%\w+]] = OpLoad %S_t %param
+; CHECK: [[e2:%\w+]] = OpCompositeExtract {{%\w+}} [[l2]] 0
+; CHECK: OpImageSampleImplicitLod {{%\w+}} [[e1]] [[e2]]
+%main = OpFunction %void None %12
+%28 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%29 = OpLoad %v2float %texCoords
+%30 = OpAccessChain %_ptr_Function_v2float %s0 %int_0
+OpStore %30 %29
+%31 = OpLoad %18 %sampler15
+%32 = OpAccessChain %_ptr_Function_18 %s0 %int_2
+OpStore %32 %31
+%33 = OpLoad %S_t %s0
+OpStore %param %33
+%34 = OpAccessChain %_ptr_Function_18 %param %int_2
+%35 = OpLoad %18 %34
+%36 = OpAccessChain %_ptr_Function_v2float %param %int_0
+%37 = OpLoad %v2float %36
+%38 = OpImageSampleImplicitLod %v4float %35 %37
+OpStore %outColor %38
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string remain =
+ R"(%foo_struct_S_t_vf2_vf21_ = OpFunction %void None %20
+%s = OpFunctionParameter %_ptr_Function_S_t
+%39 = OpLabel
+%40 = OpAccessChain %_ptr_Function_18 %s %int_2
+%41 = OpLoad %18 %40
+%42 = OpAccessChain %_ptr_Function_v2float %s %int_0
+%43 = OpLoad %v2float %42
+%44 = OpImageSampleImplicitLod %v4float %41 %43
+OpStore %outColor %44
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs + before + remain,
+ true);
+}
+
+TEST_F(LocalAccessChainConvertTest, NestedStructsConverted) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S1_t {
+ // vec4 v1;
+ // };
+ //
+ // struct S2_t {
+ // vec4 v2;
+ // S1_t s1;
+ // };
+ //
+ // void main()
+ // {
+ // S2_t s2;
+ // s2.s1.v1 = BaseColor;
+ // gl_FragColor = s2.s1.v1;
+ // }
+
+ 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 %S1_t "S1_t"
+OpMemberName %S1_t 0 "v1"
+OpName %S2_t "S2_t"
+OpMemberName %S2_t 0 "v2"
+OpMemberName %S2_t 1 "s1"
+OpName %s2 "s2"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S1_t = OpTypeStruct %v4float
+%S2_t = OpTypeStruct %v4float %S1_t
+%_ptr_Function_S2_t = OpTypePointer Function %S2_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: [[ld1:%\w+]] = OpLoad %S2_t %s2
+; CHECK: [[ex1:%\w+]] = OpCompositeInsert %S2_t [[st_id]] [[ld1]] 1 0
+; CHECK: OpStore %s2 [[ex1]]
+; CHECK: [[ld2:%\w+]] = OpLoad %S2_t %s2
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1 0
+; CHECK: OpStore %gl_FragColor [[ex2]]
+%main = OpFunction %void None %9
+%19 = OpLabel
+%s2 = OpVariable %_ptr_Function_S2_t Function
+%20 = OpLoad %v4float %BaseColor
+%21 = OpAccessChain %_ptr_Function_v4float %s2 %int_1 %int_0
+OpStore %21 %20
+%22 = OpAccessChain %_ptr_Function_v4float %s2 %int_1 %int_0
+%23 = OpLoad %v4float %22
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+ true);
+}
+
+TEST_F(LocalAccessChainConvertTest, SomeAccessChainsHaveNoUse) {
+ // Based on HLSL source code:
+ // struct S {
+ // float f;
+ // };
+
+ // float main(float input : A) : B {
+ // S local = { input };
+ // return local.f;
+ // }
+
+ const std::string predefs = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main" %in_var_A %out_var_B
+OpName %main "main"
+OpName %in_var_A "in.var.A"
+OpName %out_var_B "out.var.B"
+OpName %S "S"
+OpName %local "local"
+%int = OpTypeInt 32 1
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Output_float = OpTypePointer Output %float
+%S = OpTypeStruct %float
+%_ptr_Function_S = OpTypePointer Function %S
+%int_0 = OpConstant %int 0
+%in_var_A = OpVariable %_ptr_Input_float Input
+%out_var_B = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %8
+%15 = OpLabel
+%local = OpVariable %_ptr_Function_S Function
+%16 = OpLoad %float %in_var_A
+%17 = OpCompositeConstruct %S %16
+OpStore %local %17
+)";
+
+ const std::string before =
+ R"(
+; CHECK: [[ld:%\w+]] = OpLoad %S %local
+; CHECK: [[ex:%\w+]] = OpCompositeExtract %float [[ld]] 0
+; CHECK: OpStore %out_var_B [[ex]]
+%18 = OpAccessChain %_ptr_Function_float %local %int_0
+%19 = OpAccessChain %_ptr_Function_float %local %int_0
+%20 = OpLoad %float %18
+OpStore %out_var_B %20
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs + before, true);
+}
+
+TEST_F(LocalAccessChainConvertTest,
+ StructOfVecsOfFloatConvertedWithDecorationOnLoad) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // gl_FragColor = s0.v1;
+ // }
+
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %21 RelaxedPrecision
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(
+; CHECK: OpDecorate
+; CHECK: OpDecorate [[ld2:%\w+]] RelaxedPrecision
+; CHECK-NOT: OpDecorate
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: [[ld1:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ins:%\w+]] = OpCompositeInsert %S_t [[st_id]] [[ld1]] 1
+; CHECK: OpStore %s0 [[ins]]
+; CHECK: [[ld2]] = OpLoad %S_t %s0
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1
+; CHECK: OpStore %gl_FragColor [[ex2]]
+%main = OpFunction %void None %8
+%17 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpStore %19 %18
+%20 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+%21 = OpLoad %v4float %20
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+ true);
+}
+
+TEST_F(LocalAccessChainConvertTest,
+ StructOfVecsOfFloatConvertedWithDecorationOnStore) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // gl_FragColor = s0.v1;
+ // }
+
+ 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 %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %s0 RelaxedPrecision
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(
+; CHECK: OpDecorate
+; CHECK: OpDecorate [[ld1:%\w+]] RelaxedPrecision
+; CHECK: OpDecorate [[ins:%\w+]] RelaxedPrecision
+; CHECK-NOT: OpDecorate
+; CHECK: [[st_id:%\w+]] = OpLoad %v4float %BaseColor
+; CHECK: [[ld1]] = OpLoad %S_t %s0
+; CHECK: [[ins]] = OpCompositeInsert %S_t [[st_id]] [[ld1]] 1
+; CHECK: OpStore %s0 [[ins]]
+; CHECK: [[ld2:%\w+]] = OpLoad %S_t %s0
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %v4float [[ld2]] 1
+; CHECK: OpStore %gl_FragColor [[ex2]]
+%main = OpFunction %void None %8
+%17 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%18 = OpLoad %v4float %BaseColor
+%19 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpStore %19 %18
+%20 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+%21 = OpLoad %v4float %20
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LocalAccessChainConvertPass>(predefs_before + before,
+ true);
+}
+
+TEST_F(LocalAccessChainConvertTest, DynamicallyIndexedVarNotConverted) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // flat in int Idx;
+ // in float Bi;
+ //
+ // struct S_t {
+ // vec4 v0;
+ // vec4 v1;
+ // };
+ //
+ // void main()
+ // {
+ // S_t s0;
+ // s0.v1 = BaseColor;
+ // s0.v1[Idx] = Bi;
+ // gl_FragColor = s0.v1;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Idx %Bi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpName %s0 "s0"
+OpName %BaseColor "BaseColor"
+OpName %Idx "Idx"
+OpName %Bi "Bi"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %Idx Flat
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%S_t = OpTypeStruct %v4float %v4float
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%int = OpTypeInt 32 1
+%int_1 = OpConstant %int 1
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_int = OpTypePointer Input %int
+%Idx = OpVariable %_ptr_Input_int Input
+%_ptr_Input_float = OpTypePointer Input %float
+%Bi = OpVariable %_ptr_Input_float Input
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%22 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%23 = OpLoad %v4float %BaseColor
+%24 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+OpStore %24 %23
+%25 = OpLoad %int %Idx
+%26 = OpLoad %float %Bi
+%27 = OpAccessChain %_ptr_Function_float %s0 %int_1 %25
+OpStore %27 %26
+%28 = OpAccessChain %_ptr_Function_v4float %s0 %int_1
+%29 = OpLoad %v4float %28
+OpStore %gl_FragColor %29
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalAccessChainConvertPass>(assembly, assembly, false,
+ true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// Assorted vector and matrix types
+// Assorted struct array types
+// Assorted scalar types
+// Assorted non-target types
+// OpInBoundsAccessChain
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/local_redundancy_elimination_test.cpp b/test/opt/local_redundancy_elimination_test.cpp
new file mode 100644
index 0000000..bc4635e
--- /dev/null
+++ b/test/opt/local_redundancy_elimination_test.cpp
@@ -0,0 +1,159 @@
+// 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 "source/opt/build_module.h"
+#include "source/opt/value_number_table.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+using LocalRedundancyEliminationTest = PassTest<::testing::Test>;
+
+// Remove an instruction when it was already computed.
+TEST_F(LocalRedundancyEliminationTest, RemoveRedundantAdd) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+; CHECK: OpFAdd
+; CHECK-NOT: OpFAdd
+ %11 = OpFAdd %5 %9 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LocalRedundancyEliminationPass>(text, false);
+}
+
+// Make sure we keep instruction that are different, but look similar.
+TEST_F(LocalRedundancyEliminationTest, KeepDifferentAdd) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+; CHECK: OpFAdd
+ OpStore %8 %10
+ %11 = OpLoad %5 %8
+; CHECK: %11 = OpLoad
+ %12 = OpFAdd %5 %11 %11
+; CHECK: OpFAdd [[:%\w+]] %11 %11
+ OpReturn
+ OpFunctionEnd
+ )";
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<LocalRedundancyEliminationPass>(text, false);
+}
+
+// This test is check that the values are being propagated properly, and that
+// we are able to identify sequences of instruction that are not needed.
+TEST_F(LocalRedundancyEliminationTest, RemoveMultipleInstructions) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Uniform %5
+ %8 = OpVariable %6 Uniform
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+; CHECK: [[r1:%\w+]] = OpLoad
+ %9 = OpLoad %5 %8
+; CHECK-NEXT: [[r2:%\w+]] = OpFAdd [[:%\w+]] [[r1]] [[r1]]
+ %10 = OpFAdd %5 %9 %9
+; CHECK-NEXT: [[r3:%\w+]] = OpFMul [[:%\w+]] [[r2]] [[r1]]
+ %11 = OpFMul %5 %10 %9
+; CHECK-NOT: OpLoad
+ %12 = OpLoad %5 %8
+; CHECK-NOT: OpFAdd [[:\w+]] %12 %12
+ %13 = OpFAdd %5 %12 %12
+; CHECK-NOT: OpFMul
+ %14 = OpFMul %5 %13 %12
+; CHECK-NEXT: [[:%\w+]] = OpFAdd [[:%\w+]] [[r3]] [[r3]]
+ %15 = OpFAdd %5 %14 %11
+ OpReturn
+ OpFunctionEnd
+ )";
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndMatch<LocalRedundancyEliminationPass>(text, false);
+}
+
+// Redundant instructions in different blocks should be kept.
+TEST_F(LocalRedundancyEliminationTest, KeepInstructionsInDifferentBlocks) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %bb1 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+; CHECK: OpFAdd
+ OpBranch %bb2
+ %bb2 = OpLabel
+; CHECK: OpFAdd
+ %11 = OpFAdd %5 %9 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<LocalRedundancyEliminationPass>(text, false);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/local_single_block_elim.cpp b/test/opt/local_single_block_elim.cpp
new file mode 100644
index 0000000..e2efc5e
--- /dev/null
+++ b/test/opt/local_single_block_elim.cpp
@@ -0,0 +1,1074 @@
+// 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 LocalSingleBlockLoadStoreElimTest = PassTest<::testing::Test>;
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, SimpleStoreLoadElim) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // 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"
+%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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+%15 = OpLoad %v4float %v
+OpStore %gl_FragColor %15
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpStore %gl_FragColor %14
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs_before + before, predefs_before + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, SimpleLoadLoadElim) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // if (fi < 0)
+ // v = vec4(0.0);
+ // gl_FragData[0] = v;
+ // gl_FragData[1] = v;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragData
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %gl_FragData "gl_FragData"
+%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
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%16 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%_arr_v4float_uint_32 = OpTypeArray %v4float %uint_32
+%_ptr_Output__arr_v4float_uint_32 = OpTypePointer Output %_arr_v4float_uint_32
+%gl_FragData = OpVariable %_ptr_Output__arr_v4float_uint_32 Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%int_1 = OpConstant %int 1
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%25 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%26 = OpLoad %v4float %BaseColor
+OpStore %v %26
+%27 = OpLoad %float %fi
+%28 = OpFOrdLessThan %bool %27 %float_0
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+OpStore %v %16
+OpBranch %29
+%29 = OpLabel
+%31 = OpLoad %v4float %v
+%32 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0
+OpStore %32 %31
+%33 = OpLoad %v4float %v
+%34 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1
+OpStore %34 %33
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%25 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%26 = OpLoad %v4float %BaseColor
+OpStore %v %26
+%27 = OpLoad %float %fi
+%28 = OpFOrdLessThan %bool %27 %float_0
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+OpStore %v %16
+OpBranch %29
+%29 = OpLabel
+%31 = OpLoad %v4float %v
+%32 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0
+OpStore %32 %31
+%34 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1
+OpStore %34 %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs + before, predefs + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, StoreStoreElim) {
+ //
+ // Note first store to v is eliminated
+ //
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // v = v * 0.5;
+ // OutColor = v;
+ // }
+
+ const std::string predefs_before =
+ 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 %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %OutColor Location 0
+%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
+%float_0_5 = OpConstant %float 0.5
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%14 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%15 = OpLoad %v4float %BaseColor
+OpStore %v %15
+%16 = OpLoad %v4float %v
+%17 = OpVectorTimesScalar %v4float %16 %float_0_5
+OpStore %v %17
+%18 = OpLoad %v4float %v
+OpStore %OutColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%14 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%15 = OpLoad %v4float %BaseColor
+%17 = OpVectorTimesScalar %v4float %15 %float_0_5
+OpStore %v %17
+OpStore %OutColor %17
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs_before + before, predefs_before + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest,
+ NoStoreElimIfInterveningAccessChainLoad) {
+ //
+ // Note the first Store to %v is not eliminated due to the following access
+ // chain reference.
+ //
+ // #version 450
+ //
+ // layout(location = 0) in vec4 BaseColor0;
+ // layout(location = 1) in vec4 BaseColor1;
+ // layout(location = 2) flat in int Idx;
+ // layout(location = 0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor0;
+ // float f = v[Idx];
+ // v = BaseColor1 + vec4(0.1);
+ // OutColor = v/f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor0 %Idx %BaseColor1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor0 "BaseColor0"
+OpName %f "f"
+OpName %Idx "Idx"
+OpName %BaseColor1 "BaseColor1"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor0 Location 0
+OpDecorate %Idx Flat
+OpDecorate %Idx Location 2
+OpDecorate %BaseColor1 Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_float = OpTypePointer Function %float
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%Idx = OpVariable %_ptr_Input_int Input
+%BaseColor1 = OpVariable %_ptr_Input_v4float Input
+%float_0_100000001 = OpConstant %float 0.100000001
+%19 = OpConstantComposite %v4float %float_0_100000001 %float_0_100000001 %float_0_100000001 %float_0_100000001
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%22 = OpLoad %v4float %BaseColor0
+OpStore %v %22
+%23 = OpLoad %int %Idx
+%24 = OpAccessChain %_ptr_Function_float %v %23
+%25 = OpLoad %float %24
+OpStore %f %25
+%26 = OpLoad %v4float %BaseColor1
+%27 = OpFAdd %v4float %26 %19
+OpStore %v %27
+%28 = OpLoad %v4float %v
+%29 = OpLoad %float %f
+%30 = OpCompositeConstruct %v4float %29 %29 %29 %29
+%31 = OpFDiv %v4float %28 %30
+OpStore %OutColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%22 = OpLoad %v4float %BaseColor0
+OpStore %v %22
+%23 = OpLoad %int %Idx
+%24 = OpAccessChain %_ptr_Function_float %v %23
+%25 = OpLoad %float %24
+OpStore %f %25
+%26 = OpLoad %v4float %BaseColor1
+%27 = OpFAdd %v4float %26 %19
+OpStore %v %27
+%30 = OpCompositeConstruct %v4float %25 %25 %25 %25
+%31 = OpFDiv %v4float %27 %30
+OpStore %OutColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs + before, predefs + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, NoElimIfInterveningAccessChainStore) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // flat in int Idx;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // v[Idx] = 0;
+ // gl_FragColor = v;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Idx %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Idx "Idx"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %Idx Flat
+%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
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%Idx = OpVariable %_ptr_Input_int Input
+%float_0 = OpConstant %float 0
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %8
+%18 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%19 = OpLoad %v4float %BaseColor
+OpStore %v %19
+%20 = OpLoad %int %Idx
+%21 = OpAccessChain %_ptr_Function_float %v %20
+OpStore %21 %float_0
+%22 = OpLoad %v4float %v
+OpStore %gl_FragColor %22
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(assembly, assembly,
+ false, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, NoElimIfInterveningFunctionCall) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void foo() {
+ // }
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // foo();
+ // gl_FragColor = v;
+ // }
+
+ const std::string assembly =
+ 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 %foo_ "foo("
+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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %8
+%14 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%15 = OpLoad %v4float %BaseColor
+OpStore %v %15
+%16 = OpFunctionCall %void %foo_
+%17 = OpLoad %v4float %v
+OpStore %gl_FragColor %17
+OpReturn
+OpFunctionEnd
+%foo_ = OpFunction %void None %8
+%18 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(assembly, assembly,
+ false, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, ElimIfCopyObjectInFunction) {
+ // Note: SPIR-V hand edited to insert CopyObject
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // vec4 v1 = BaseColor;
+ // gl_FragData[0] = v1;
+ // vec4 v2 = BaseColor * 0.5;
+ // gl_FragData[1] = v2;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %gl_FragData
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v1 "v1"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragData "gl_FragData"
+OpName %v2 "v2"
+%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
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%_arr_v4float_uint_32 = OpTypeArray %v4float %uint_32
+%_ptr_Output__arr_v4float_uint_32 = OpTypePointer Output %_arr_v4float_uint_32
+%gl_FragData = OpVariable %_ptr_Output__arr_v4float_uint_32 Output
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%float_0_5 = OpConstant %float 0.5
+%int_1 = OpConstant %int 1
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%22 = OpLabel
+%v1 = OpVariable %_ptr_Function_v4float Function
+%v2 = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %v1 %23
+%24 = OpLoad %v4float %v1
+%25 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0
+OpStore %25 %24
+%26 = OpLoad %v4float %BaseColor
+%27 = OpVectorTimesScalar %v4float %26 %float_0_5
+%28 = OpCopyObject %_ptr_Function_v4float %v2
+OpStore %28 %27
+%29 = OpLoad %v4float %28
+%30 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1
+OpStore %30 %29
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%22 = OpLabel
+%v1 = OpVariable %_ptr_Function_v4float Function
+%v2 = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %v1 %23
+%25 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_0
+OpStore %25 %23
+%26 = OpLoad %v4float %BaseColor
+%27 = OpVectorTimesScalar %v4float %26 %float_0_5
+%28 = OpCopyObject %_ptr_Function_v4float %v2
+OpStore %28 %27
+%30 = OpAccessChain %_ptr_Output_v4float %gl_FragData %int_1
+OpStore %30 %27
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs + before, predefs + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, ElimOpaque) {
+ // SPIR-V not representable in GLSL; not generatable from HLSL
+ // at the moment
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %outColor %texCoords
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %S_t "S_t"
+OpMemberName %S_t 0 "v0"
+OpMemberName %S_t 1 "v1"
+OpMemberName %S_t 2 "smp"
+OpName %outColor "outColor"
+OpName %sampler15 "sampler15"
+OpName %s0 "s0"
+OpName %texCoords "texCoords"
+OpName %param "param"
+OpDecorate %sampler15 DescriptorSet 0
+%void = OpTypeVoid
+%12 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%outColor = OpVariable %_ptr_Output_v4float Output
+%17 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%18 = OpTypeSampledImage %17
+%S_t = OpTypeStruct %v2float %v2float %18
+%_ptr_Function_S_t = OpTypePointer Function %S_t
+%20 = OpTypeFunction %void %_ptr_Function_S_t
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+%_ptr_Function_18 = OpTypePointer Function %18
+%sampler15 = OpVariable %_ptr_UniformConstant_18 UniformConstant
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_2 = OpConstant %int 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%texCoords = OpVariable %_ptr_Input_v2float Input
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %12
+%28 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%29 = OpLoad %v2float %texCoords
+%30 = OpLoad %S_t %s0
+%31 = OpCompositeInsert %S_t %29 %30 0
+OpStore %s0 %31
+%32 = OpLoad %18 %sampler15
+%33 = OpLoad %S_t %s0
+%34 = OpCompositeInsert %S_t %32 %33 2
+OpStore %s0 %34
+%35 = OpLoad %S_t %s0
+OpStore %param %35
+%36 = OpLoad %S_t %param
+%37 = OpCompositeExtract %18 %36 2
+%38 = OpLoad %S_t %param
+%39 = OpCompositeExtract %v2float %38 0
+%40 = OpImageSampleImplicitLod %v4float %37 %39
+OpStore %outColor %40
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %12
+%28 = OpLabel
+%s0 = OpVariable %_ptr_Function_S_t Function
+%param = OpVariable %_ptr_Function_S_t Function
+%29 = OpLoad %v2float %texCoords
+%30 = OpLoad %S_t %s0
+%31 = OpCompositeInsert %S_t %29 %30 0
+%32 = OpLoad %18 %sampler15
+%34 = OpCompositeInsert %S_t %32 %31 2
+OpStore %s0 %34
+OpStore %param %34
+%37 = OpCompositeExtract %18 %34 2
+%39 = OpCompositeExtract %v2float %34 0
+%40 = OpImageSampleImplicitLod %v4float %37 %39
+OpStore %outColor %40
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs + before, predefs + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, PositiveAndNegativeCallTree) {
+ // Note that the call tree function bar is optimized, but foo is not
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // vec4 foo(vec4 v1)
+ // {
+ // vec4 t = v1;
+ // return t;
+ // }
+ //
+ // vec4 bar(vec4 v1)
+ // {
+ // vec4 t = v1;
+ // return t;
+ // }
+ //
+ // void main()
+ // {
+ // gl_FragColor = bar(BaseColor);
+ // }
+
+ 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 %foo_vf4_ "foo(vf4;"
+OpName %v1 "v1"
+OpName %bar_vf4_ "bar(vf4;"
+OpName %v1_0 "v1"
+OpName %t "t"
+OpName %t_0 "t"
+OpName %gl_FragColor "gl_FragColor"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+%void = OpTypeVoid
+%13 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%17 = OpTypeFunction %v4float %_ptr_Function_v4float
+%_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
+%main = OpFunction %void None %13
+%20 = OpLabel
+%param = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %v4float %BaseColor
+OpStore %param %21
+%22 = OpFunctionCall %v4float %bar_vf4_ %param
+OpStore %gl_FragColor %22
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string before =
+ R"(%foo_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%23 = OpLabel
+%t = OpVariable %_ptr_Function_v4float Function
+%24 = OpLoad %v4float %v1
+OpStore %t %24
+%25 = OpLoad %v4float %t
+OpReturnValue %25
+OpFunctionEnd
+%bar_vf4_ = OpFunction %v4float None %17
+%v1_0 = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+%t_0 = OpVariable %_ptr_Function_v4float Function
+%27 = OpLoad %v4float %v1_0
+OpStore %t_0 %27
+%28 = OpLoad %v4float %t_0
+OpReturnValue %28
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%foo_vf4_ = OpFunction %v4float None %17
+%v1 = OpFunctionParameter %_ptr_Function_v4float
+%23 = OpLabel
+%t = OpVariable %_ptr_Function_v4float Function
+%24 = OpLoad %v4float %v1
+OpStore %t %24
+%25 = OpLoad %v4float %t
+OpReturnValue %25
+OpFunctionEnd
+%bar_vf4_ = OpFunction %v4float None %17
+%v1_0 = OpFunctionParameter %_ptr_Function_v4float
+%26 = OpLabel
+%t_0 = OpVariable %_ptr_Function_v4float Function
+%27 = OpLoad %v4float %v1_0
+OpStore %t_0 %27
+OpReturnValue %27
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs + before, predefs + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, PointerVariable) {
+ // Test that checks if a pointer variable is removed.
+
+ const std::string before =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpMemberDecorate %_struct_6 0 Offset 0
+OpDecorate %_struct_6 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_struct_6 = OpTypeStruct %int
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%26 = OpLoad %_ptr_Uniform__struct_5 %24
+%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0
+%28 = OpLoad %v4float %27
+%29 = OpCopyObject %v4float %28
+OpStore %2 %28
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpMemberDecorate %_struct_6 0 Offset 0
+OpDecorate %_struct_6 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_struct_6 = OpTypeStruct %int
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0
+%28 = OpLoad %v4float %27
+%29 = OpCopyObject %v4float %28
+OpStore %2 %28
+OpReturn
+OpFunctionEnd
+)";
+
+ // Relax logical pointers to allow pointer allocations.
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ValidatorOptions()->relax_logical_pointer = true;
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(before, after, true,
+ true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, RedundantStore) {
+ // Test that checks if a pointer variable is removed.
+ 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"
+%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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpBranch %16
+%16 = OpLabel
+%15 = OpLoad %v4float %v
+OpStore %v %15
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpBranch %16
+%16 = OpLabel
+%15 = OpLoad %v4float %v
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs_before + before, predefs_before + after, true, true);
+}
+
+TEST_F(LocalSingleBlockLoadStoreElimTest, RedundantStore2) {
+ // Test that checks if a pointer variable is removed.
+ 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"
+%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
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpBranch %16
+%16 = OpLabel
+%15 = OpLoad %v4float %v
+OpStore %v %15
+%17 = OpLoad %v4float %v
+OpStore %v %17
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %7
+%13 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%14 = OpLoad %v4float %BaseColor
+OpStore %v %14
+OpBranch %16
+%16 = OpLabel
+%15 = OpLoad %v4float %v
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs_before + before, predefs_before + after, true, true);
+}
+
+// Test that that an unused OpAccessChain between two store does does not
+// hinders the removal of the first store. We need to check this because
+// local-access-chain-convert does always remove the OpAccessChain instructions
+// that become dead.
+
+TEST_F(LocalSingleBlockLoadStoreElimTest,
+ StoreElimIfInterveningUnusedAccessChain) {
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor0 %Idx %BaseColor1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor0 "BaseColor0"
+OpName %Idx "Idx"
+OpName %BaseColor1 "BaseColor1"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor0 Location 0
+OpDecorate %Idx Flat
+OpDecorate %Idx Location 2
+OpDecorate %BaseColor1 Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Function_float = OpTypePointer Function %float
+%int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%Idx = OpVariable %_ptr_Input_int Input
+%BaseColor1 = OpVariable %_ptr_Input_v4float Input
+%float_0_100000001 = OpConstant %float 0.100000001
+%19 = OpConstantComposite %v4float %float_0_100000001 %float_0_100000001 %float_0_100000001 %float_0_100000001
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %BaseColor0
+OpStore %v %22
+%23 = OpLoad %int %Idx
+%24 = OpAccessChain %_ptr_Function_float %v %23
+%26 = OpLoad %v4float %BaseColor1
+%27 = OpFAdd %v4float %26 %19
+OpStore %v %27
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%22 = OpLoad %v4float %BaseColor0
+%23 = OpLoad %int %Idx
+%24 = OpAccessChain %_ptr_Function_float %v %23
+%26 = OpLoad %v4float %BaseColor1
+%27 = OpFAdd %v4float %26 %19
+OpStore %v %27
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<LocalSingleBlockLoadStoreElimPass>(
+ predefs + before, predefs + after, true, true);
+}
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// Other target variable types
+// InBounds Access Chains
+// Check for correctness in the presence of function calls
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/local_single_store_elim_test.cpp b/test/opt/local_single_store_elim_test.cpp
new file mode 100644
index 0000000..5fd0217
--- /dev/null
+++ b/test/opt/local_single_store_elim_test.cpp
@@ -0,0 +1,857 @@
+// 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 LocalSingleStoreElimTest = PassTest<::testing::Test>;
+
+TEST_F(LocalSingleStoreElimTest, PositiveAndNegative) {
+ // Single store to v is optimized. Multiple store to
+ // f is not optimized.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // float f = fi;
+ // if (f < 0)
+ // f = 0.0;
+ // gl_FragColor = v + f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %f "f"
+OpName %fi "fi"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = 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
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+OpStore %f %21
+%22 = OpLoad %float %f
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+OpStore %f %float_0
+OpBranch %24
+%24 = OpLabel
+%26 = OpLoad %v4float %v
+%27 = OpLoad %float %f
+%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
+%29 = OpFAdd %v4float %26 %28
+OpStore %gl_FragColor %29
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+OpStore %f %21
+%22 = OpLoad %float %f
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+OpStore %f %float_0
+OpBranch %24
+%24 = OpLabel
+%27 = OpLoad %float %f
+%28 = OpCompositeConstruct %v4float %27 %27 %27 %27
+%29 = OpFAdd %v4float %20 %28
+OpStore %gl_FragColor %29
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSingleStoreElimTest, ThreeStores) {
+ // Three stores to multiple loads of v is not optimized.
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %r "r"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = 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
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%r = OpVariable %_ptr_Function_v4float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+%22 = OpFOrdLessThan %bool %21 %float_0
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %25
+%24 = OpLabel
+%26 = OpLoad %v4float %v
+OpStore %v %26
+OpStore %r %26
+OpBranch %23
+%25 = OpLabel
+%27 = OpLoad %v4float %v
+%28 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %float_1
+OpStore %v %28
+%29 = OpFSub %v4float %28 %27
+OpStore %r %29
+OpBranch %23
+%23 = OpLabel
+%30 = OpLoad %v4float %r
+OpStore %gl_FragColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+ predefs + before, true, true);
+}
+
+TEST_F(LocalSingleStoreElimTest, MultipleLoads) {
+ // Single store to multiple loads of v is optimized.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // float f = fi;
+ // if (f < 0)
+ // f = 0.0;
+ // gl_FragColor = v + f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %fi "fi"
+OpName %r "r"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = 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
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%r = OpVariable %_ptr_Function_v4float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+%22 = OpFOrdLessThan %bool %21 %float_0
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %25
+%24 = OpLabel
+%26 = OpLoad %v4float %v
+OpStore %r %26
+OpBranch %23
+%25 = OpLabel
+%27 = OpLoad %v4float %v
+%28 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %float_1
+%29 = OpFSub %v4float %28 %27
+OpStore %r %29
+OpBranch %23
+%23 = OpLabel
+%30 = OpLoad %v4float %r
+OpStore %gl_FragColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%r = OpVariable %_ptr_Function_v4float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+%22 = OpFOrdLessThan %bool %21 %float_0
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %25
+%24 = OpLabel
+OpStore %r %20
+OpBranch %23
+%25 = OpLabel
+%28 = OpCompositeConstruct %v4float %float_1 %float_1 %float_1 %float_1
+%29 = OpFSub %v4float %28 %20
+OpStore %r %29
+OpBranch %23
+%23 = OpLabel
+%30 = OpLoad %v4float %r
+OpStore %gl_FragColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSingleStoreElimTest, NoStoreElimWithInterveningAccessChainLoad) {
+ // Last load of v is eliminated, but access chain load and store of v isn't
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // float f = v[3];
+ // gl_FragColor = v * f;
+ // }
+
+ 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 %f "f"
+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
+%_ptr_Function_float = OpTypePointer Function %float
+%uint = OpTypeInt 32 0
+%uint_3 = OpConstant %uint 3
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%17 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%18 = OpLoad %v4float %BaseColor
+OpStore %v %18
+%19 = OpAccessChain %_ptr_Function_float %v %uint_3
+%20 = OpLoad %float %19
+OpStore %f %20
+%21 = OpLoad %v4float %v
+%22 = OpLoad %float %f
+%23 = OpVectorTimesScalar %v4float %21 %22
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%17 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%18 = OpLoad %v4float %BaseColor
+OpStore %v %18
+%19 = OpAccessChain %_ptr_Function_float %v %uint_3
+%20 = OpLoad %float %19
+OpStore %f %20
+%23 = OpVectorTimesScalar %v4float %18 %20
+OpStore %gl_FragColor %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSingleStoreElimTest, NoReplaceOfDominatingPartialStore) {
+ // Note: SPIR-V hand edited to initialize v to vec4(0.0)
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // vec4 v;
+ // float v[1] = 1.0;
+ // gl_FragColor = v;
+ // }
+
+ const std::string assembly =
+ 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
+%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
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Function_float = OpTypePointer Function %float
+%_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
+%main = OpFunction %void None %7
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function %12
+%20 = OpAccessChain %_ptr_Function_float %v %uint_1
+OpStore %20 %float_1
+%21 = OpLoad %v4float %v
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(assembly, assembly, true,
+ true);
+}
+
+TEST_F(LocalSingleStoreElimTest, ElimIfCopyObjectInFunction) {
+ // Note: hand edited to insert OpCopyObject
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // float f = fi;
+ // if (f < 0)
+ // f = 0.0;
+ // gl_FragColor = v + f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %fi %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %f "f"
+OpName %fi "fi"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = 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
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+OpStore %f %21
+%22 = OpLoad %float %f
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+OpStore %f %float_0
+OpBranch %24
+%24 = OpLabel
+%26 = OpCopyObject %_ptr_Function_v4float %v
+%27 = OpLoad %v4float %26
+%28 = OpLoad %float %f
+%29 = OpCompositeConstruct %v4float %28 %28 %28 %28
+%30 = OpFAdd %v4float %27 %29
+OpStore %gl_FragColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%19 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%f = OpVariable %_ptr_Function_float Function
+%20 = OpLoad %v4float %BaseColor
+OpStore %v %20
+%21 = OpLoad %float %fi
+OpStore %f %21
+%22 = OpLoad %float %f
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+OpStore %f %float_0
+OpBranch %24
+%24 = OpLabel
+%26 = OpCopyObject %_ptr_Function_v4float %v
+%28 = OpLoad %float %f
+%29 = OpCompositeConstruct %v4float %28 %28 %28 %28
+%30 = OpFAdd %v4float %20 %29
+OpStore %gl_FragColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSingleStoreElimTest, NoOptIfStoreNotDominating) {
+ // Single store to f not optimized because it does not dominate
+ // the load.
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float fi;
+ //
+ // void main()
+ // {
+ // float f;
+ // if (fi < 0)
+ // f = 0.5;
+ // if (fi < 0)
+ // gl_FragColor = BaseColor * f;
+ // else
+ // gl_FragColor = BaseColor;
+ // }
+
+ const std::string assembly =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %fi %gl_FragColor %BaseColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %fi "fi"
+OpName %f "f"
+OpName %gl_FragColor "gl_FragColor"
+OpName %BaseColor "BaseColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0_5 = OpConstant %float 0.5
+%v4float = OpTypeVector %float 4
+%_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
+%main = OpFunction %void None %8
+%18 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%19 = OpLoad %float %fi
+%20 = OpFOrdLessThan %bool %19 %float_0
+OpSelectionMerge %21 None
+OpBranchConditional %20 %22 %21
+%22 = OpLabel
+OpStore %f %float_0_5
+OpBranch %21
+%21 = OpLabel
+%23 = OpLoad %float %fi
+%24 = OpFOrdLessThan %bool %23 %float_0
+OpSelectionMerge %25 None
+OpBranchConditional %24 %26 %27
+%26 = OpLabel
+%28 = OpLoad %v4float %BaseColor
+%29 = OpLoad %float %f
+%30 = OpVectorTimesScalar %v4float %28 %29
+OpStore %gl_FragColor %30
+OpBranch %25
+%27 = OpLabel
+%31 = OpLoad %v4float %BaseColor
+OpStore %gl_FragColor %31
+OpBranch %25
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(assembly, assembly, true,
+ true);
+}
+
+TEST_F(LocalSingleStoreElimTest, OptInitializedVariableLikeStore) {
+ // Initialized variable f is optimized like it was a store.
+ // Note: The SPIR-V was edited to turn the store to f to an
+ // an initialization.
+ //
+ // #version 140
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // gl_FragColor = vec4(f);
+ // }
+
+ 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 %f "f"
+OpName %gl_FragColor "gl_FragColor"
+OpDecorate %gl_FragColor Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %6
+%12 = OpLabel
+%f = OpVariable %_ptr_Function_float Function %float_0
+%13 = OpLoad %float %f
+%14 = OpCompositeConstruct %v4float %13 %13 %13 %13
+OpStore %gl_FragColor %14
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %6
+%12 = OpLabel
+%f = OpVariable %_ptr_Function_float Function %float_0
+%14 = OpCompositeConstruct %v4float %float_0 %float_0 %float_0 %float_0
+OpStore %gl_FragColor %14
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSingleStoreElimTest, PointerVariable) {
+ // Test that checks if a pointer variable is removed.
+
+ const std::string before =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpMemberDecorate %_struct_6 0 Offset 0
+OpDecorate %_struct_6 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_struct_6 = OpTypeStruct %int
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%26 = OpLoad %_ptr_Uniform__struct_5 %24
+%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0
+%28 = OpLoad %v4float %27
+%29 = OpCopyObject %v4float %28
+OpStore %2 %28
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpMemberDecorate %_struct_6 0 Offset 0
+OpDecorate %_struct_6 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_struct_6 = OpTypeStruct %int
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0
+%28 = OpLoad %v4float %27
+%29 = OpCopyObject %v4float %28
+OpStore %2 %28
+OpReturn
+OpFunctionEnd
+)";
+
+ // Relax logical pointers to allow pointer allocations.
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ValidatorOptions()->relax_logical_pointer = true;
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(before, after, true, true);
+}
+
+// Test that that an unused OpAccessChain between a store and a use does does
+// not hinders the replacement of the use. We need to check this because
+// local-access-chain-convert does always remove the OpAccessChain instructions
+// that become dead.
+
+TEST_F(LocalSingleStoreElimTest,
+ StoreElimWithUnusedInterveningAccessChainLoad) {
+ // Last load of v is eliminated, but access chain load and store of v isn't
+ //
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // float f = v[3];
+ // gl_FragColor = v * f;
+ // }
+
+ 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
+%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
+%_ptr_Function_float = OpTypePointer Function %float
+%uint = OpTypeInt 32 0
+%uint_3 = OpConstant %uint 3
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%17 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%18 = OpLoad %v4float %BaseColor
+OpStore %v %18
+%19 = OpAccessChain %_ptr_Function_float %v %uint_3
+%21 = OpLoad %v4float %v
+OpStore %gl_FragColor %21
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%17 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%18 = OpLoad %v4float %BaseColor
+OpStore %v %18
+%19 = OpAccessChain %_ptr_Function_float %v %uint_3
+OpStore %gl_FragColor %18
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<LocalSingleStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// Other types
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/local_ssa_elim_test.cpp b/test/opt/local_ssa_elim_test.cpp
new file mode 100644
index 0000000..b2961a2
--- /dev/null
+++ b/test/opt/local_ssa_elim_test.cpp
@@ -0,0 +1,1834 @@
+// 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 <memory>
+#include <string>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using LocalSSAElimTest = PassTest<::testing::Test>;
+
+TEST_F(LocalSSAElimTest, ForLoop) {
+ // #version 140
+ //
+ // in vec4 BC;
+ // out float fo;
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // for (int i=0; i<4; i++) {
+ // f = f + BC[i];
+ // }
+ // fo = f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %f "f"
+OpName %i "i"
+OpName %BC "BC"
+OpName %fo "fo"
+%void = OpTypeVoid
+%8 = 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_4 = OpConstant %int 4
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BC = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %8
+%22 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %23
+%23 = OpLabel
+OpLoopMerge %24 %25 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpLoad %int %i
+%28 = OpSLessThan %bool %27 %int_4
+OpBranchConditional %28 %29 %24
+%29 = OpLabel
+%30 = OpLoad %float %f
+%31 = OpLoad %int %i
+%32 = OpAccessChain %_ptr_Input_float %BC %31
+%33 = OpLoad %float %32
+%34 = OpFAdd %float %30 %33
+OpStore %f %34
+OpBranch %25
+%25 = OpLabel
+%35 = OpLoad %int %i
+%36 = OpIAdd %int %35 %int_1
+OpStore %i %36
+OpBranch %23
+%24 = OpLabel
+%37 = OpLoad %float %f
+OpStore %fo %37
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%22 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %23
+%23 = OpLabel
+%39 = OpPhi %float %float_0 %22 %34 %25
+%38 = OpPhi %int %int_0 %22 %36 %25
+OpLoopMerge %24 %25 None
+OpBranch %26
+%26 = OpLabel
+%28 = OpSLessThan %bool %38 %int_4
+OpBranchConditional %28 %29 %24
+%29 = OpLabel
+%32 = OpAccessChain %_ptr_Input_float %BC %38
+%33 = OpLoad %float %32
+%34 = OpFAdd %float %39 %33
+OpStore %f %34
+OpBranch %25
+%25 = OpLabel
+%36 = OpIAdd %int %38 %int_1
+OpStore %i %36
+OpBranch %23
+%24 = OpLabel
+OpStore %fo %39
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, NestedForLoop) {
+ // #version 450
+ //
+ // layout (location=0) in mat4 BC;
+ // layout (location=0) out float fo;
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // for (int i=0; i<4; i++)
+ // for (int j=0; j<4; j++)
+ // f = f + BC[i][j];
+ // fo = f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %f "f"
+OpName %i "i"
+OpName %j "j"
+OpName %BC "BC"
+OpName %fo "fo"
+OpDecorate %BC Location 0
+OpDecorate %fo Location 0
+%void = OpTypeVoid
+%9 = 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_4 = OpConstant %int 4
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%mat4v4float = OpTypeMatrix %v4float 4
+%_ptr_Input_mat4v4float = OpTypePointer Input %mat4v4float
+%BC = OpVariable %_ptr_Input_mat4v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%24 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%j = OpVariable %_ptr_Function_int Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %25
+%25 = OpLabel
+%26 = OpLoad %int %i
+%27 = OpSLessThan %bool %26 %int_4
+OpLoopMerge %28 %29 None
+OpBranchConditional %27 %30 %28
+%30 = OpLabel
+OpStore %j %int_0
+OpBranch %31
+%31 = OpLabel
+%32 = OpLoad %int %j
+%33 = OpSLessThan %bool %32 %int_4
+OpLoopMerge %29 %34 None
+OpBranchConditional %33 %34 %29
+%34 = OpLabel
+%35 = OpLoad %float %f
+%36 = OpLoad %int %i
+%37 = OpLoad %int %j
+%38 = OpAccessChain %_ptr_Input_float %BC %36 %37
+%39 = OpLoad %float %38
+%40 = OpFAdd %float %35 %39
+OpStore %f %40
+%41 = OpLoad %int %j
+%42 = OpIAdd %int %41 %int_1
+OpStore %j %42
+OpBranch %31
+%29 = OpLabel
+%43 = OpLoad %int %i
+%44 = OpIAdd %int %43 %int_1
+OpStore %i %44
+OpBranch %25
+%28 = OpLabel
+%45 = OpLoad %float %f
+OpStore %fo %45
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%24 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%j = OpVariable %_ptr_Function_int Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %25
+%25 = OpLabel
+%47 = OpPhi %float %float_0 %24 %50 %29
+%46 = OpPhi %int %int_0 %24 %44 %29
+%27 = OpSLessThan %bool %46 %int_4
+OpLoopMerge %28 %29 None
+OpBranchConditional %27 %30 %28
+%30 = OpLabel
+OpStore %j %int_0
+OpBranch %31
+%31 = OpLabel
+%50 = OpPhi %float %47 %30 %40 %34
+%48 = OpPhi %int %int_0 %30 %42 %34
+%33 = OpSLessThan %bool %48 %int_4
+OpLoopMerge %29 %34 None
+OpBranchConditional %33 %34 %29
+%34 = OpLabel
+%38 = OpAccessChain %_ptr_Input_float %BC %46 %48
+%39 = OpLoad %float %38
+%40 = OpFAdd %float %50 %39
+OpStore %f %40
+%42 = OpIAdd %int %48 %int_1
+OpStore %j %42
+OpBranch %31
+%29 = OpLabel
+%44 = OpIAdd %int %46 %int_1
+OpStore %i %44
+OpBranch %25
+%28 = OpLabel
+OpStore %fo %47
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, ForLoopWithContinue) {
+ // #version 140
+ //
+ // in vec4 BC;
+ // out float fo;
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // for (int i=0; i<4; i++) {
+ // float t = BC[i];
+ // if (t < 0.0)
+ // continue;
+ // f = f + t;
+ // }
+ // fo = f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+)";
+
+ const std::string names =
+ R"(OpName %main "main"
+OpName %f "f"
+OpName %i "i"
+OpName %t "t"
+OpName %BC "BC"
+OpName %fo "fo"
+)";
+
+ const std::string predefs2 =
+ R"(%void = OpTypeVoid
+%9 = 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_4 = OpConstant %int 4
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BC = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%23 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %24
+%24 = OpLabel
+OpLoopMerge %25 %26 None
+OpBranch %27
+%27 = OpLabel
+%28 = OpLoad %int %i
+%29 = OpSLessThan %bool %28 %int_4
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+%31 = OpLoad %int %i
+%32 = OpAccessChain %_ptr_Input_float %BC %31
+%33 = OpLoad %float %32
+OpStore %t %33
+%34 = OpLoad %float %t
+%35 = OpFOrdLessThan %bool %34 %float_0
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %36
+%37 = OpLabel
+OpBranch %26
+%36 = OpLabel
+%38 = OpLoad %float %f
+%39 = OpLoad %float %t
+%40 = OpFAdd %float %38 %39
+OpStore %f %40
+OpBranch %26
+%26 = OpLabel
+%41 = OpLoad %int %i
+%42 = OpIAdd %int %41 %int_1
+OpStore %i %42
+OpBranch %24
+%25 = OpLabel
+%43 = OpLoad %float %f
+OpStore %fo %43
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%23 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %24
+%24 = OpLabel
+%45 = OpPhi %float %float_0 %23 %47 %26
+%44 = OpPhi %int %int_0 %23 %42 %26
+OpLoopMerge %25 %26 None
+OpBranch %27
+%27 = OpLabel
+%29 = OpSLessThan %bool %44 %int_4
+OpBranchConditional %29 %30 %25
+%30 = OpLabel
+%32 = OpAccessChain %_ptr_Input_float %BC %44
+%33 = OpLoad %float %32
+OpStore %t %33
+%35 = OpFOrdLessThan %bool %33 %float_0
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %36
+%37 = OpLabel
+OpBranch %26
+%36 = OpLabel
+%40 = OpFAdd %float %45 %33
+OpStore %f %40
+OpBranch %26
+%26 = OpLabel
+%47 = OpPhi %float %45 %37 %40 %36
+%42 = OpIAdd %int %44 %int_1
+OpStore %i %42
+OpBranch %24
+%25 = OpLabel
+OpStore %fo %45
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(
+ predefs + names + predefs2 + before, predefs + names + predefs2 + after,
+ true, true);
+}
+
+TEST_F(LocalSSAElimTest, ForLoopWithBreak) {
+ // #version 140
+ //
+ // in vec4 BC;
+ // out float fo;
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // for (int i=0; i<4; i++) {
+ // float t = f + BC[i];
+ // if (t > 1.0)
+ // break;
+ // f = t;
+ // }
+ // fo = f;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %f "f"
+OpName %i "i"
+OpName %t "t"
+OpName %BC "BC"
+OpName %fo "fo"
+%void = OpTypeVoid
+%9 = 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_4 = OpConstant %int 4
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BC = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%24 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %25
+%25 = OpLabel
+OpLoopMerge %26 %27 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpLoad %int %i
+%30 = OpSLessThan %bool %29 %int_4
+OpBranchConditional %30 %31 %26
+%31 = OpLabel
+%32 = OpLoad %float %f
+%33 = OpLoad %int %i
+%34 = OpAccessChain %_ptr_Input_float %BC %33
+%35 = OpLoad %float %34
+%36 = OpFAdd %float %32 %35
+OpStore %t %36
+%37 = OpLoad %float %t
+%38 = OpFOrdGreaterThan %bool %37 %float_1
+OpSelectionMerge %39 None
+OpBranchConditional %38 %40 %39
+%40 = OpLabel
+OpBranch %26
+%39 = OpLabel
+%41 = OpLoad %float %t
+OpStore %f %41
+OpBranch %27
+%27 = OpLabel
+%42 = OpLoad %int %i
+%43 = OpIAdd %int %42 %int_1
+OpStore %i %43
+OpBranch %25
+%26 = OpLabel
+%44 = OpLoad %float %f
+OpStore %fo %44
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%24 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %25
+%25 = OpLabel
+%46 = OpPhi %float %float_0 %24 %36 %27
+%45 = OpPhi %int %int_0 %24 %43 %27
+OpLoopMerge %26 %27 None
+OpBranch %28
+%28 = OpLabel
+%30 = OpSLessThan %bool %45 %int_4
+OpBranchConditional %30 %31 %26
+%31 = OpLabel
+%34 = OpAccessChain %_ptr_Input_float %BC %45
+%35 = OpLoad %float %34
+%36 = OpFAdd %float %46 %35
+OpStore %t %36
+%38 = OpFOrdGreaterThan %bool %36 %float_1
+OpSelectionMerge %39 None
+OpBranchConditional %38 %40 %39
+%40 = OpLabel
+OpBranch %26
+%39 = OpLabel
+OpStore %f %36
+OpBranch %27
+%27 = OpLabel
+%43 = OpIAdd %int %45 %int_1
+OpStore %i %43
+OpBranch %25
+%26 = OpLabel
+OpStore %fo %46
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, SwapProblem) {
+ // #version 140
+ //
+ // in float fe;
+ // out float fo;
+ //
+ // void main()
+ // {
+ // float f1 = 0.0;
+ // float f2 = 1.0;
+ // int ie = int(fe);
+ // for (int i=0; i<ie; i++) {
+ // float t = f1;
+ // f1 = f2;
+ // f2 = t;
+ // }
+ // fo = f1;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %fe %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %f1 "f1"
+OpName %f2 "f2"
+OpName %ie "ie"
+OpName %fe "fe"
+OpName %i "i"
+OpName %t "t"
+OpName %fo "fo"
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_float = OpTypePointer Input %float
+%fe = OpVariable %_ptr_Input_float Input
+%int_0 = OpConstant %int 0
+%bool = OpTypeBool
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %11
+%23 = OpLabel
+%f1 = OpVariable %_ptr_Function_float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%ie = OpVariable %_ptr_Function_int Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f1 %float_0
+OpStore %f2 %float_1
+%24 = OpLoad %float %fe
+%25 = OpConvertFToS %int %24
+OpStore %ie %25
+OpStore %i %int_0
+OpBranch %26
+%26 = OpLabel
+OpLoopMerge %27 %28 None
+OpBranch %29
+%29 = OpLabel
+%30 = OpLoad %int %i
+%31 = OpLoad %int %ie
+%32 = OpSLessThan %bool %30 %31
+OpBranchConditional %32 %33 %27
+%33 = OpLabel
+%34 = OpLoad %float %f1
+OpStore %t %34
+%35 = OpLoad %float %f2
+OpStore %f1 %35
+%36 = OpLoad %float %t
+OpStore %f2 %36
+OpBranch %28
+%28 = OpLabel
+%37 = OpLoad %int %i
+%38 = OpIAdd %int %37 %int_1
+OpStore %i %38
+OpBranch %26
+%27 = OpLabel
+%39 = OpLoad %float %f1
+OpStore %fo %39
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %11
+%23 = OpLabel
+%f1 = OpVariable %_ptr_Function_float Function
+%f2 = OpVariable %_ptr_Function_float Function
+%ie = OpVariable %_ptr_Function_int Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f1 %float_0
+OpStore %f2 %float_1
+%24 = OpLoad %float %fe
+%25 = OpConvertFToS %int %24
+OpStore %ie %25
+OpStore %i %int_0
+OpBranch %26
+%26 = OpLabel
+%43 = OpPhi %float %float_1 %23 %42 %28
+%42 = OpPhi %float %float_0 %23 %43 %28
+%40 = OpPhi %int %int_0 %23 %38 %28
+OpLoopMerge %27 %28 None
+OpBranch %29
+%29 = OpLabel
+%32 = OpSLessThan %bool %40 %25
+OpBranchConditional %32 %33 %27
+%33 = OpLabel
+OpStore %t %42
+OpStore %f1 %43
+OpStore %f2 %42
+OpBranch %28
+%28 = OpLabel
+%38 = OpIAdd %int %40 %int_1
+OpStore %i %38
+OpBranch %26
+%27 = OpLabel
+OpStore %fo %42
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, LostCopyProblem) {
+ // #version 140
+ //
+ // in vec4 BC;
+ // out float fo;
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // float t;
+ // for (int i=0; i<4; i++) {
+ // t = f;
+ // f = f + BC[i];
+ // if (f > 1.0)
+ // break;
+ // }
+ // fo = t;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %f "f"
+OpName %i "i"
+OpName %t "t"
+OpName %BC "BC"
+OpName %fo "fo"
+%void = OpTypeVoid
+%9 = 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_4 = OpConstant %int 4
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BC = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%24 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %25
+%25 = OpLabel
+OpLoopMerge %26 %27 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpLoad %int %i
+%30 = OpSLessThan %bool %29 %int_4
+OpBranchConditional %30 %31 %26
+%31 = OpLabel
+%32 = OpLoad %float %f
+OpStore %t %32
+%33 = OpLoad %float %f
+%34 = OpLoad %int %i
+%35 = OpAccessChain %_ptr_Input_float %BC %34
+%36 = OpLoad %float %35
+%37 = OpFAdd %float %33 %36
+OpStore %f %37
+%38 = OpLoad %float %f
+%39 = OpFOrdGreaterThan %bool %38 %float_1
+OpSelectionMerge %40 None
+OpBranchConditional %39 %41 %40
+%41 = OpLabel
+OpBranch %26
+%40 = OpLabel
+OpBranch %27
+%27 = OpLabel
+%42 = OpLoad %int %i
+%43 = OpIAdd %int %42 %int_1
+OpStore %i %43
+OpBranch %25
+%26 = OpLabel
+%44 = OpLoad %float %t
+OpStore %fo %44
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%49 = OpUndef %float
+%main = OpFunction %void None %9
+%24 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+%t = OpVariable %_ptr_Function_float Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %25
+%25 = OpLabel
+%46 = OpPhi %float %float_0 %24 %37 %27
+%45 = OpPhi %int %int_0 %24 %43 %27
+%48 = OpPhi %float %49 %24 %46 %27
+OpLoopMerge %26 %27 None
+OpBranch %28
+%28 = OpLabel
+%30 = OpSLessThan %bool %45 %int_4
+OpBranchConditional %30 %31 %26
+%31 = OpLabel
+OpStore %t %46
+%35 = OpAccessChain %_ptr_Input_float %BC %45
+%36 = OpLoad %float %35
+%37 = OpFAdd %float %46 %36
+OpStore %f %37
+%39 = OpFOrdGreaterThan %bool %37 %float_1
+OpSelectionMerge %40 None
+OpBranchConditional %39 %41 %40
+%41 = OpLabel
+OpBranch %26
+%40 = OpLabel
+OpBranch %27
+%27 = OpLabel
+%43 = OpIAdd %int %45 %int_1
+OpStore %i %43
+OpBranch %25
+%26 = OpLabel
+%47 = OpPhi %float %48 %28 %46 %41
+OpStore %fo %47
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, IfThenElse) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float f;
+ //
+ // void main()
+ // {
+ // vec4 v;
+ // if (f >= 0)
+ // v = BaseColor * 0.5;
+ // else
+ // v = BaseColor + 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" %f %BaseColor %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %f "f"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Input_float = OpTypePointer Input %float
+%f = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%float_0_5 = OpConstant %float 0.5
+%float_1 = OpConstant %float 1
+%18 = 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
+%20 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %float %f
+%22 = OpFOrdGreaterThanEqual %bool %21 %float_0
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %25
+%24 = OpLabel
+%26 = OpLoad %v4float %BaseColor
+%27 = OpVectorTimesScalar %v4float %26 %float_0_5
+OpStore %v %27
+OpBranch %23
+%25 = OpLabel
+%28 = OpLoad %v4float %BaseColor
+%29 = OpFAdd %v4float %28 %18
+OpStore %v %29
+OpBranch %23
+%23 = OpLabel
+%30 = OpLoad %v4float %v
+OpStore %gl_FragColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %8
+%20 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%21 = OpLoad %float %f
+%22 = OpFOrdGreaterThanEqual %bool %21 %float_0
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %25
+%24 = OpLabel
+%26 = OpLoad %v4float %BaseColor
+%27 = OpVectorTimesScalar %v4float %26 %float_0_5
+OpStore %v %27
+OpBranch %23
+%25 = OpLabel
+%28 = OpLoad %v4float %BaseColor
+%29 = OpFAdd %v4float %28 %18
+OpStore %v %29
+OpBranch %23
+%23 = OpLabel
+%31 = OpPhi %v4float %27 %24 %29 %25
+OpStore %gl_FragColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, IfThen) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float f;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // if (f <= 0)
+ // v = v * 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 %f %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %f "f"
+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
+%_ptr_Input_float = OpTypePointer Input %float
+%f = OpVariable %_ptr_Input_float Input
+%float_0 = OpConstant %float 0
+%bool = OpTypeBool
+%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 %8
+%18 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%19 = OpLoad %v4float %BaseColor
+OpStore %v %19
+%20 = OpLoad %float %f
+%21 = OpFOrdLessThanEqual %bool %20 %float_0
+OpSelectionMerge %22 None
+OpBranchConditional %21 %23 %22
+%23 = OpLabel
+%24 = OpLoad %v4float %v
+%25 = OpVectorTimesScalar %v4float %24 %float_0_5
+OpStore %v %25
+OpBranch %22
+%22 = OpLabel
+%26 = OpLoad %v4float %v
+OpStore %gl_FragColor %26
+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
+%20 = OpLoad %float %f
+%21 = OpFOrdLessThanEqual %bool %20 %float_0
+OpSelectionMerge %22 None
+OpBranchConditional %21 %23 %22
+%23 = OpLabel
+%25 = OpVectorTimesScalar %v4float %19 %float_0_5
+OpStore %v %25
+OpBranch %22
+%22 = OpLabel
+%27 = OpPhi %v4float %19 %18 %25 %23
+OpStore %gl_FragColor %27
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, Switch) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float f;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // int i = int(f);
+ // switch (i) {
+ // case 0:
+ // v = v * 0.25;
+ // break;
+ // case 1:
+ // v = v * 0.625;
+ // break;
+ // case 2:
+ // v = v * 0.75;
+ // break;
+ // default:
+ // break;
+ // }
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %f %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %i "i"
+OpName %f "f"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = 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
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_float = OpTypePointer Input %float
+%f = OpVariable %_ptr_Input_float Input
+%float_0_25 = OpConstant %float 0.25
+%float_0_625 = OpConstant %float 0.625
+%float_0_75 = OpConstant %float 0.75
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%i = OpVariable %_ptr_Function_int Function
+%22 = OpLoad %v4float %BaseColor
+OpStore %v %22
+%23 = OpLoad %float %f
+%24 = OpConvertFToS %int %23
+OpStore %i %24
+%25 = OpLoad %int %i
+OpSelectionMerge %26 None
+OpSwitch %25 %27 0 %28 1 %29 2 %30
+%27 = OpLabel
+OpBranch %26
+%28 = OpLabel
+%31 = OpLoad %v4float %v
+%32 = OpVectorTimesScalar %v4float %31 %float_0_25
+OpStore %v %32
+OpBranch %26
+%29 = OpLabel
+%33 = OpLoad %v4float %v
+%34 = OpVectorTimesScalar %v4float %33 %float_0_625
+OpStore %v %34
+OpBranch %26
+%30 = OpLabel
+%35 = OpLoad %v4float %v
+%36 = OpVectorTimesScalar %v4float %35 %float_0_75
+OpStore %v %36
+OpBranch %26
+%26 = OpLabel
+%37 = OpLoad %v4float %v
+OpStore %gl_FragColor %37
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%i = OpVariable %_ptr_Function_int Function
+%22 = OpLoad %v4float %BaseColor
+OpStore %v %22
+%23 = OpLoad %float %f
+%24 = OpConvertFToS %int %23
+OpStore %i %24
+OpSelectionMerge %26 None
+OpSwitch %24 %27 0 %28 1 %29 2 %30
+%27 = OpLabel
+OpBranch %26
+%28 = OpLabel
+%32 = OpVectorTimesScalar %v4float %22 %float_0_25
+OpStore %v %32
+OpBranch %26
+%29 = OpLabel
+%34 = OpVectorTimesScalar %v4float %22 %float_0_625
+OpStore %v %34
+OpBranch %26
+%30 = OpLabel
+%36 = OpVectorTimesScalar %v4float %22 %float_0_75
+OpStore %v %36
+OpBranch %26
+%26 = OpLabel
+%38 = OpPhi %v4float %22 %27 %32 %28 %34 %29 %36 %30
+OpStore %gl_FragColor %38
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, SwitchWithFallThrough) {
+ // #version 140
+ //
+ // in vec4 BaseColor;
+ // in float f;
+ //
+ // void main()
+ // {
+ // vec4 v = BaseColor;
+ // int i = int(f);
+ // switch (i) {
+ // case 0:
+ // v = v * 0.25;
+ // break;
+ // case 1:
+ // v = v + 0.25;
+ // case 2:
+ // v = v * 0.75;
+ // break;
+ // default:
+ // break;
+ // }
+ // gl_FragColor = v;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %f %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %i "i"
+OpName %f "f"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%9 = 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
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_float = OpTypePointer Input %float
+%f = OpVariable %_ptr_Input_float Input
+%float_0_25 = OpConstant %float 0.25
+%float_0_75 = OpConstant %float 0.75
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %9
+%20 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%i = OpVariable %_ptr_Function_int Function
+%21 = OpLoad %v4float %BaseColor
+OpStore %v %21
+%22 = OpLoad %float %f
+%23 = OpConvertFToS %int %22
+OpStore %i %23
+%24 = OpLoad %int %i
+OpSelectionMerge %25 None
+OpSwitch %24 %26 0 %27 1 %28 2 %29
+%26 = OpLabel
+OpBranch %25
+%27 = OpLabel
+%30 = OpLoad %v4float %v
+%31 = OpVectorTimesScalar %v4float %30 %float_0_25
+OpStore %v %31
+OpBranch %25
+%28 = OpLabel
+%32 = OpLoad %v4float %v
+%33 = OpCompositeConstruct %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25
+%34 = OpFAdd %v4float %32 %33
+OpStore %v %34
+OpBranch %29
+%29 = OpLabel
+%35 = OpLoad %v4float %v
+%36 = OpVectorTimesScalar %v4float %35 %float_0_75
+OpStore %v %36
+OpBranch %25
+%25 = OpLabel
+%37 = OpLoad %v4float %v
+OpStore %gl_FragColor %37
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %9
+%20 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function
+%i = OpVariable %_ptr_Function_int Function
+%21 = OpLoad %v4float %BaseColor
+OpStore %v %21
+%22 = OpLoad %float %f
+%23 = OpConvertFToS %int %22
+OpStore %i %23
+OpSelectionMerge %25 None
+OpSwitch %23 %26 0 %27 1 %28 2 %29
+%26 = OpLabel
+OpBranch %25
+%27 = OpLabel
+%31 = OpVectorTimesScalar %v4float %21 %float_0_25
+OpStore %v %31
+OpBranch %25
+%28 = OpLabel
+%33 = OpCompositeConstruct %v4float %float_0_25 %float_0_25 %float_0_25 %float_0_25
+%34 = OpFAdd %v4float %21 %33
+OpStore %v %34
+OpBranch %29
+%29 = OpLabel
+%38 = OpPhi %v4float %21 %20 %34 %28
+%36 = OpVectorTimesScalar %v4float %38 %float_0_75
+OpStore %v %36
+OpBranch %25
+%25 = OpLabel
+%39 = OpPhi %v4float %21 %26 %31 %27 %36 %29
+OpStore %gl_FragColor %39
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(predefs + before,
+ predefs + after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, DontPatchPhiInLoopHeaderThatIsNotAVar) {
+ // From https://github.com/KhronosGroup/SPIRV-Tools/issues/826
+ // Don't try patching the (%16 %7) value/predecessor pair in the OpPhi.
+ // That OpPhi is unrelated to this optimization: we did not set that up
+ // in the SSA initialization for the loop header block.
+ // The pass should be a no-op on this module.
+
+ const std::string before = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+%void = OpTypeVoid
+%3 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%float_1 = OpConstant %float 1
+%1 = OpFunction %void None %3
+%6 = OpLabel
+OpBranch %7
+%7 = OpLabel
+%8 = OpPhi %float %float_1 %6 %9 %7
+%9 = OpFAdd %float %8 %float_1
+OpLoopMerge %10 %7 None
+OpBranch %7
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(before, before, true, true);
+}
+
+TEST_F(LocalSSAElimTest, OptInitializedVariableLikeStore) {
+ // Note: SPIR-V edited to change store to v into variable initialization
+ //
+ // #version 450
+ //
+ // layout (location=0) in vec4 iColor;
+ // layout (location=1) in float fi;
+ // layout (location=0) out vec4 oColor;
+ //
+ // void main()
+ // {
+ // vec4 v = vec4(0.0);
+ // if (fi < 0.0)
+ // v.x = iColor.x;
+ // oColor = v;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %fi %iColor %oColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %fi "fi"
+OpName %iColor "iColor"
+OpName %oColor "oColor"
+OpDecorate %fi Location 1
+OpDecorate %iColor Location 0
+OpDecorate %oColor Location 0
+%void = OpTypeVoid
+%8 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%float_0 = OpConstant %float 0
+%13 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%_ptr_Input_float = OpTypePointer Input %float
+%fi = OpVariable %_ptr_Input_float Input
+%bool = OpTypeBool
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%iColor = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%oColor = OpVariable %_ptr_Output_v4float Output
+)";
+
+ const std::string func_before =
+ R"(%main = OpFunction %void None %8
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function %13
+%22 = OpLoad %float %fi
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+%26 = OpAccessChain %_ptr_Input_float %iColor %uint_0
+%27 = OpLoad %float %26
+%28 = OpLoad %v4float %v
+%29 = OpCompositeInsert %v4float %27 %28 0
+OpStore %v %29
+OpBranch %24
+%24 = OpLabel
+%30 = OpLoad %v4float %v
+OpStore %oColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string func_after =
+ R"(%main = OpFunction %void None %8
+%21 = OpLabel
+%v = OpVariable %_ptr_Function_v4float Function %13
+%22 = OpLoad %float %fi
+%23 = OpFOrdLessThan %bool %22 %float_0
+OpSelectionMerge %24 None
+OpBranchConditional %23 %25 %24
+%25 = OpLabel
+%26 = OpAccessChain %_ptr_Input_float %iColor %uint_0
+%27 = OpLoad %float %26
+%29 = OpCompositeInsert %v4float %27 %13 0
+OpStore %v %29
+OpBranch %24
+%24 = OpLabel
+%31 = OpPhi %v4float %13 %21 %29 %25
+OpStore %oColor %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(
+ predefs + func_before, predefs + func_after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, PointerVariable) {
+ // Test that checks if a pointer variable is removed.
+
+ const std::string before =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpMemberDecorate %_struct_6 0 Offset 0
+OpDecorate %_struct_6 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_struct_6 = OpTypeStruct %int
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%26 = OpLoad %_ptr_Uniform__struct_5 %24
+%27 = OpAccessChain %_ptr_Uniform_v4float %26 %int_0 %uint_0 %int_0
+%28 = OpLoad %v4float %27
+%29 = OpCopyObject %v4float %28
+OpStore %2 %28
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpMemberDecorate %_struct_3 0 Offset 0
+OpDecorate %_runtimearr__struct_3 ArrayStride 16
+OpMemberDecorate %_struct_5 0 Offset 0
+OpDecorate %_struct_5 BufferBlock
+OpMemberDecorate %_struct_6 0 Offset 0
+OpDecorate %_struct_6 BufferBlock
+OpDecorate %2 Location 0
+OpDecorate %7 DescriptorSet 0
+OpDecorate %7 Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%_struct_3 = OpTypeStruct %v4float
+%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
+%_struct_5 = OpTypeStruct %_runtimearr__struct_3
+%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
+%_struct_6 = OpTypeStruct %int
+%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
+%_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
+%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
+%int_0 = OpConstant %int 0
+%uint_0 = OpConstant %uint 0
+%2 = OpVariable %_ptr_Output_v4float Output
+%7 = OpVariable %_ptr_Uniform__struct_5 Uniform
+%1 = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
+OpStore %24 %7
+%27 = OpAccessChain %_ptr_Uniform_v4float %7 %int_0 %uint_0 %int_0
+%28 = OpLoad %v4float %27
+%29 = OpCopyObject %v4float %28
+OpStore %2 %28
+OpReturn
+OpFunctionEnd
+)";
+
+ // Relax logical pointers to allow pointer allocations.
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ValidatorOptions()->relax_logical_pointer = true;
+ SinglePassRunAndCheck<LocalMultiStoreElimPass>(before, after, true, true);
+}
+
+TEST_F(LocalSSAElimTest, VerifyInstToBlockMap) {
+ // #version 140
+ //
+ // in vec4 BC;
+ // out float fo;
+ //
+ // void main()
+ // {
+ // float f = 0.0;
+ // for (int i=0; i<4; i++) {
+ // f = f + BC[i];
+ // }
+ // fo = f;
+ // }
+
+ const std::string text = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BC %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %f "f"
+OpName %i "i"
+OpName %BC "BC"
+OpName %fo "fo"
+%void = OpTypeVoid
+%8 = 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_4 = OpConstant %int 4
+%bool = OpTypeBool
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BC = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%int_1 = OpConstant %int 1
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %8
+%22 = OpLabel
+%f = OpVariable %_ptr_Function_float Function
+%i = OpVariable %_ptr_Function_int Function
+OpStore %f %float_0
+OpStore %i %int_0
+OpBranch %23
+%23 = OpLabel
+OpLoopMerge %24 %25 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpLoad %int %i
+%28 = OpSLessThan %bool %27 %int_4
+OpBranchConditional %28 %29 %24
+%29 = OpLabel
+%30 = OpLoad %float %f
+%31 = OpLoad %int %i
+%32 = OpAccessChain %_ptr_Input_float %BC %31
+%33 = OpLoad %float %32
+%34 = OpFAdd %float %30 %33
+OpStore %f %34
+OpBranch %25
+%25 = OpLabel
+%35 = OpLoad %int %i
+%36 = OpIAdd %int %35 %int_1
+OpStore %i %36
+OpBranch %23
+%24 = OpLabel
+%37 = OpLoad %float %f
+OpStore %fo %37
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(nullptr, context);
+
+ // Force the instruction to block mapping to get built.
+ context->get_instr_block(27u);
+
+ auto pass = MakeUnique<LocalMultiStoreElimPass>();
+ pass->SetMessageConsumer(nullptr);
+ const auto status = pass->Run(context.get());
+ EXPECT_TRUE(status == Pass::Status::SuccessWithChange);
+}
+
+TEST_F(LocalSSAElimTest, CompositeExtractProblem) {
+ const std::string spv_asm = R"(
+ OpCapability Tessellation
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint TessellationControl %2 "main" %16 %17 %18 %20 %22 %26 %27 %30 %31
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %uint = OpTypeInt 32 0
+ %uint_3 = OpConstant %uint 3
+ %v3float = OpTypeVector %float 3
+ %v2float = OpTypeVector %float 2
+ %_struct_11 = OpTypeStruct %v4float %v4float %v4float %v3float %v3float %v2float %v2float
+%_arr__struct_11_uint_3 = OpTypeArray %_struct_11 %uint_3
+%_ptr_Function__arr__struct_11_uint_3 = OpTypePointer Function %_arr__struct_11_uint_3
+%_arr_v4float_uint_3 = OpTypeArray %v4float %uint_3
+%_ptr_Input__arr_v4float_uint_3 = OpTypePointer Input %_arr_v4float_uint_3
+ %16 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input
+ %17 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input
+ %18 = OpVariable %_ptr_Input__arr_v4float_uint_3 Input
+%_ptr_Input_uint = OpTypePointer Input %uint
+ %20 = OpVariable %_ptr_Input_uint Input
+%_ptr_Output__arr_v4float_uint_3 = OpTypePointer Output %_arr_v4float_uint_3
+ %22 = OpVariable %_ptr_Output__arr_v4float_uint_3 Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_arr_v3float_uint_3 = OpTypeArray %v3float %uint_3
+%_ptr_Input__arr_v3float_uint_3 = OpTypePointer Input %_arr_v3float_uint_3
+ %26 = OpVariable %_ptr_Input__arr_v3float_uint_3 Input
+ %27 = OpVariable %_ptr_Input__arr_v3float_uint_3 Input
+%_arr_v2float_uint_3 = OpTypeArray %v2float %uint_3
+%_ptr_Input__arr_v2float_uint_3 = OpTypePointer Input %_arr_v2float_uint_3
+ %30 = OpVariable %_ptr_Input__arr_v2float_uint_3 Input
+ %31 = OpVariable %_ptr_Input__arr_v2float_uint_3 Input
+%_ptr_Function__struct_11 = OpTypePointer Function %_struct_11
+ %2 = OpFunction %void None %4
+ %33 = OpLabel
+ %66 = OpVariable %_ptr_Function__arr__struct_11_uint_3 Function
+ %34 = OpLoad %_arr_v4float_uint_3 %16
+ %35 = OpLoad %_arr_v4float_uint_3 %17
+ %36 = OpLoad %_arr_v4float_uint_3 %18
+ %37 = OpLoad %_arr_v3float_uint_3 %26
+ %38 = OpLoad %_arr_v3float_uint_3 %27
+ %39 = OpLoad %_arr_v2float_uint_3 %30
+ %40 = OpLoad %_arr_v2float_uint_3 %31
+ %41 = OpCompositeExtract %v4float %34 0
+ %42 = OpCompositeExtract %v4float %35 0
+ %43 = OpCompositeExtract %v4float %36 0
+ %44 = OpCompositeExtract %v3float %37 0
+ %45 = OpCompositeExtract %v3float %38 0
+ %46 = OpCompositeExtract %v2float %39 0
+ %47 = OpCompositeExtract %v2float %40 0
+ %48 = OpCompositeConstruct %_struct_11 %41 %42 %43 %44 %45 %46 %47
+ %49 = OpCompositeExtract %v4float %34 1
+ %50 = OpCompositeExtract %v4float %35 1
+ %51 = OpCompositeExtract %v4float %36 1
+ %52 = OpCompositeExtract %v3float %37 1
+ %53 = OpCompositeExtract %v3float %38 1
+ %54 = OpCompositeExtract %v2float %39 1
+ %55 = OpCompositeExtract %v2float %40 1
+ %56 = OpCompositeConstruct %_struct_11 %49 %50 %51 %52 %53 %54 %55
+ %57 = OpCompositeExtract %v4float %34 2
+ %58 = OpCompositeExtract %v4float %35 2
+ %59 = OpCompositeExtract %v4float %36 2
+ %60 = OpCompositeExtract %v3float %37 2
+ %61 = OpCompositeExtract %v3float %38 2
+ %62 = OpCompositeExtract %v2float %39 2
+ %63 = OpCompositeExtract %v2float %40 2
+ %64 = OpCompositeConstruct %_struct_11 %57 %58 %59 %60 %61 %62 %63
+ %65 = OpCompositeConstruct %_arr__struct_11_uint_3 %48 %56 %64
+ %67 = OpLoad %uint %20
+
+; CHECK OpStore {{%\d+}} [[store_source:%\d+]]
+ OpStore %66 %65
+ %68 = OpAccessChain %_ptr_Function__struct_11 %66 %67
+
+; This load was being removed, because %_ptr_Function__struct_11 was being
+; wrongfully considered an SSA target.
+; CHECK OpLoad %_struct_11 %68
+ %69 = OpLoad %_struct_11 %68
+
+; Similarly, %69 cannot be replaced with %65.
+; CHECK-NOT: OpCompositeExtract %v4float [[store_source]] 0
+ %70 = OpCompositeExtract %v4float %69 0
+
+ %71 = OpAccessChain %_ptr_Output_v4float %22 %67
+ OpStore %71 %70
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<SSARewritePass>(spv_asm, true);
+}
+
+// Test that the RelaxedPrecision decoration on the variable to added to the
+// result of the OpPhi instruction.
+TEST_F(LocalSSAElimTest, DecoratedVariable) {
+ const std::string spv_asm = R"(
+; CHECK: OpDecorate [[var:%\w+]] RelaxedPrecision
+; CHECK: OpDecorate [[phi_id:%\w+]] RelaxedPrecision
+; CHECK: [[phi_id]] = OpPhi
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %2 "main"
+ OpDecorate %v RelaxedPrecision
+ %void = OpTypeVoid
+ %func_t = OpTypeFunction %void
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %int = OpTypeInt 32 0
+ %int_p = OpTypePointer Function %int
+ %int_1 = OpConstant %int 1
+ %int_0 = OpConstant %int 0
+ %2 = OpFunction %void None %func_t
+ %33 = OpLabel
+ %v = OpVariable %int_p Function
+ OpSelectionMerge %merge None
+ OpBranchConditional %true %l1 %l2
+ %l1 = OpLabel
+ OpStore %v %int_1
+ OpBranch %merge
+ %l2 = OpLabel
+ OpStore %v %int_0
+ OpBranch %merge
+ %merge = OpLabel
+ %ld = OpLoad %int %v
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<SSARewritePass>(spv_asm, true);
+}
+
+// Test that the RelaxedPrecision decoration on the variable to added to the
+// result of the OpPhi instruction.
+TEST_F(LocalSSAElimTest, MultipleEdges) {
+ const std::string spv_asm = R"(
+ ; CHECK: OpSelectionMerge
+ ; CHECK: [[header_bb:%\w+]] = OpLabel
+ ; CHECK-NOT: OpLabel
+ ; CHECK: OpSwitch {{%\w+}} {{%\w+}} 76 [[bb1:%\w+]] 17 [[bb2:%\w+]]
+ ; CHECK-SAME: 4 [[bb2]]
+ ; CHECK: [[bb2]] = OpLabel
+ ; CHECK-NEXT: OpPhi [[type:%\w+]] [[val:%\w+]] [[header_bb]] %int_0 [[bb1]]
+ 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
+ %int = OpTypeInt 32 1
+ %_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %false = OpConstantFalse %bool
+ %int_1 = OpConstant %int 1
+ %4 = OpFunction %void None %3
+ %5 = OpLabel
+ %8 = OpVariable %_ptr_Function_int Function
+ OpBranch %10
+ %10 = OpLabel
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ OpBranchConditional %true %11 %12
+ %11 = OpLabel
+ OpSelectionMerge %19 None
+ OpBranchConditional %false %18 %19
+ %18 = OpLabel
+ OpSelectionMerge %22 None
+ OpSwitch %int_0 %22 76 %20 17 %21 4 %21
+ %20 = OpLabel
+ %23 = OpLoad %int %8
+ OpStore %8 %int_0
+ OpBranch %21
+ %21 = OpLabel
+ OpBranch %22
+ %22 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<SSARewritePass>(spv_asm, true);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// No optimization in the presence of
+// access chains
+// function calls
+// OpCopyMemory?
+// unsupported extensions
+// Others?
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/CMakeLists.txt b/test/opt/loop_optimizations/CMakeLists.txt
new file mode 100644
index 0000000..e362078
--- /dev/null
+++ b/test/opt/loop_optimizations/CMakeLists.txt
@@ -0,0 +1,41 @@
+# 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.
+
+
+add_spvtools_unittest(TARGET opt_loops
+ SRCS ../function_utils.h
+ dependence_analysis.cpp
+ dependence_analysis_helpers.cpp
+ fusion_compatibility.cpp
+ fusion_illegal.cpp
+ fusion_legal.cpp
+ fusion_pass.cpp
+ hoist_all_loop_types.cpp
+ hoist_double_nested_loops.cpp
+ hoist_from_independent_loops.cpp
+ hoist_simple_case.cpp
+ hoist_single_nested_loops.cpp
+ hoist_without_preheader.cpp
+ lcssa.cpp
+ loop_descriptions.cpp
+ loop_fission.cpp
+ nested_loops.cpp
+ peeling.cpp
+ peeling_pass.cpp
+ unroll_assumptions.cpp
+ unroll_simple.cpp
+ unswitch.cpp
+ LIBS SPIRV-Tools-opt
+ PCH_FILE pch_test_opt_loop
+)
diff --git a/test/opt/loop_optimizations/dependence_analysis.cpp b/test/opt/loop_optimizations/dependence_analysis.cpp
new file mode 100644
index 0000000..8aeb20a
--- /dev/null
+++ b/test/opt/loop_optimizations/dependence_analysis.cpp
@@ -0,0 +1,4205 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/iterator.h"
+#include "source/opt/loop_dependence.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "source/opt/tree_iterator.h"
+#include "test/opt//assembly_builder.h"
+#include "test/opt//function_utils.h"
+#include "test/opt//pass_fixture.h"
+#include "test/opt//pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using DependencyAnalysis = ::testing::Test;
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void main(){
+ int[10] arr;
+ int[10] arr2;
+ int a = 2;
+ for (int i = 0; i < 10; i++) {
+ arr[a] = arr[3];
+ arr[a*2] = arr[a+3];
+ arr[6] = arr2[6];
+ arr[a+5] = arr2[7];
+ }
+}
+*/
+TEST(DependencyAnalysis, ZIV) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %25 "arr"
+ OpName %39 "arr2"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 2
+ %11 = OpConstant %6 0
+ %18 = OpConstant %6 10
+ %19 = OpTypeBool
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 10
+ %23 = OpTypeArray %6 %22
+ %24 = OpTypePointer Function %23
+ %27 = OpConstant %6 3
+ %38 = OpConstant %6 6
+ %44 = OpConstant %6 5
+ %46 = OpConstant %6 7
+ %51 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %25 = OpVariable %24 Function
+ %39 = OpVariable %24 Function
+ OpBranch %12
+ %12 = OpLabel
+ %53 = OpPhi %6 %11 %5 %52 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %19 %53 %18
+ OpBranchConditional %20 %13 %14
+ %13 = OpLabel
+ %28 = OpAccessChain %7 %25 %27
+ %29 = OpLoad %6 %28
+ %30 = OpAccessChain %7 %25 %9
+ OpStore %30 %29
+ %32 = OpIMul %6 %9 %9
+ %34 = OpIAdd %6 %9 %27
+ %35 = OpAccessChain %7 %25 %34
+ %36 = OpLoad %6 %35
+ %37 = OpAccessChain %7 %25 %32
+ OpStore %37 %36
+ %40 = OpAccessChain %7 %39 %38
+ %41 = OpLoad %6 %40
+ %42 = OpAccessChain %7 %25 %38
+ OpStore %42 %41
+ %45 = OpIAdd %6 %9 %44
+ %47 = OpAccessChain %7 %39 %46
+ %48 = OpLoad %6 %47
+ %49 = OpAccessChain %7 %25 %45
+ OpStore %49 %48
+ OpBranch %15
+ %15 = OpLabel
+ %52 = OpIAdd %6 %53 %51
+ OpBranch %12
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 13)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // 29 -> 30 tests looking through constants.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(29),
+ store[0], &distance_vector));
+ }
+
+ // 36 -> 37 tests looking through additions.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(36),
+ store[1], &distance_vector));
+ }
+
+ // 41 -> 42 tests looking at same index across two different arrays.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(41),
+ store[2], &distance_vector));
+ }
+
+ // 48 -> 49 tests looking through additions for same index in two different
+ // arrays.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(48),
+ store[3], &distance_vector));
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+layout(location = 0) in vec4 c;
+void main(){
+ int[10] arr;
+ int[10] arr2;
+ int[10] arr3;
+ int[10] arr4;
+ int[10] arr5;
+ int N = int(c.x);
+ for (int i = 0; i < N; i++) {
+ arr[2*N] = arr[N];
+ arr2[2*N+1] = arr2[N];
+ arr3[2*N] = arr3[N-1];
+ arr4[N] = arr5[N];
+ }
+}
+*/
+TEST(DependencyAnalysis, SymbolicZIV) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %12
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %12 "c"
+ OpName %33 "arr"
+ OpName %41 "arr2"
+ OpName %50 "arr3"
+ OpName %58 "arr4"
+ OpName %60 "arr5"
+ OpDecorate %12 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpTypeFloat 32
+ %10 = OpTypeVector %9 4
+ %11 = OpTypePointer Input %10
+ %12 = OpVariable %11 Input
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 0
+ %15 = OpTypePointer Input %9
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %30 = OpConstant %13 10
+ %31 = OpTypeArray %6 %30
+ %32 = OpTypePointer Function %31
+ %34 = OpConstant %6 2
+ %44 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %33 = OpVariable %32 Function
+ %41 = OpVariable %32 Function
+ %50 = OpVariable %32 Function
+ %58 = OpVariable %32 Function
+ %60 = OpVariable %32 Function
+ %16 = OpAccessChain %15 %12 %14
+ %17 = OpLoad %9 %16
+ %18 = OpConvertFToS %6 %17
+ OpBranch %21
+ %21 = OpLabel
+ %67 = OpPhi %6 %20 %5 %66 %24
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %29 = OpSLessThan %28 %67 %18
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ %36 = OpIMul %6 %34 %18
+ %38 = OpAccessChain %7 %33 %18
+ %39 = OpLoad %6 %38
+ %40 = OpAccessChain %7 %33 %36
+ OpStore %40 %39
+ %43 = OpIMul %6 %34 %18
+ %45 = OpIAdd %6 %43 %44
+ %47 = OpAccessChain %7 %41 %18
+ %48 = OpLoad %6 %47
+ %49 = OpAccessChain %7 %41 %45
+ OpStore %49 %48
+ %52 = OpIMul %6 %34 %18
+ %54 = OpISub %6 %18 %44
+ %55 = OpAccessChain %7 %50 %54
+ %56 = OpLoad %6 %55
+ %57 = OpAccessChain %7 %50 %52
+ OpStore %57 %56
+ %62 = OpAccessChain %7 %60 %18
+ %63 = OpLoad %6 %62
+ %64 = OpAccessChain %7 %58 %18
+ OpStore %64 %63
+ OpBranch %24
+ %24 = OpLabel
+ %66 = OpIAdd %6 %67 %44
+ OpBranch %21
+ %23 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 22)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // independent due to loop bounds (won't enter if N <= 0).
+ // 39 -> 40 tests looking through symbols and multiplicaiton.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(39),
+ store[0], &distance_vector));
+ }
+
+ // 48 -> 49 tests looking through symbols and multiplication + addition.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(48),
+ store[1], &distance_vector));
+ }
+
+ // 56 -> 57 tests looking through symbols and arithmetic on load and store.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(56),
+ store[2], &distance_vector));
+ }
+
+ // independent as different arrays
+ // 63 -> 64 tests looking through symbols and load/store from/to different
+ // arrays.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(63),
+ store[3], &distance_vector));
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void a(){
+ int[10] arr;
+ int[11] arr2;
+ int[20] arr3;
+ int[20] arr4;
+ int a = 2;
+ for (int i = 0; i < 10; i++) {
+ arr[i] = arr[i];
+ arr2[i] = arr2[i+1];
+ arr3[i] = arr3[i-1];
+ arr4[2*i] = arr4[i];
+ }
+}
+void b(){
+ int[10] arr;
+ int[11] arr2;
+ int[20] arr3;
+ int[20] arr4;
+ int a = 2;
+ for (int i = 10; i > 0; i--) {
+ arr[i] = arr[i];
+ arr2[i] = arr2[i+1];
+ arr3[i] = arr3[i-1];
+ arr4[2*i] = arr4[i];
+ }
+}
+
+void main() {
+ a();
+ b();
+}
+*/
+TEST(DependencyAnalysis, SIV) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %12 "a"
+ OpName %14 "i"
+ OpName %29 "arr"
+ OpName %38 "arr2"
+ OpName %49 "arr3"
+ OpName %56 "arr4"
+ OpName %65 "a"
+ OpName %66 "i"
+ OpName %74 "arr"
+ OpName %80 "arr2"
+ OpName %87 "arr3"
+ OpName %94 "arr4"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %13 = OpConstant %10 2
+ %15 = OpConstant %10 0
+ %22 = OpConstant %10 10
+ %23 = OpTypeBool
+ %25 = OpTypeInt 32 0
+ %26 = OpConstant %25 10
+ %27 = OpTypeArray %10 %26
+ %28 = OpTypePointer Function %27
+ %35 = OpConstant %25 11
+ %36 = OpTypeArray %10 %35
+ %37 = OpTypePointer Function %36
+ %41 = OpConstant %10 1
+ %46 = OpConstant %25 20
+ %47 = OpTypeArray %10 %46
+ %48 = OpTypePointer Function %47
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %103 = OpFunctionCall %2 %6
+ %104 = OpFunctionCall %2 %8
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %12 = OpVariable %11 Function
+ %14 = OpVariable %11 Function
+ %29 = OpVariable %28 Function
+ %38 = OpVariable %37 Function
+ %49 = OpVariable %48 Function
+ %56 = OpVariable %48 Function
+ OpStore %12 %13
+ OpStore %14 %15
+ OpBranch %16
+ %16 = OpLabel
+ %105 = OpPhi %10 %15 %7 %64 %19
+ OpLoopMerge %18 %19 None
+ OpBranch %20
+ %20 = OpLabel
+ %24 = OpSLessThan %23 %105 %22
+ OpBranchConditional %24 %17 %18
+ %17 = OpLabel
+ %32 = OpAccessChain %11 %29 %105
+ %33 = OpLoad %10 %32
+ %34 = OpAccessChain %11 %29 %105
+ OpStore %34 %33
+ %42 = OpIAdd %10 %105 %41
+ %43 = OpAccessChain %11 %38 %42
+ %44 = OpLoad %10 %43
+ %45 = OpAccessChain %11 %38 %105
+ OpStore %45 %44
+ %52 = OpISub %10 %105 %41
+ %53 = OpAccessChain %11 %49 %52
+ %54 = OpLoad %10 %53
+ %55 = OpAccessChain %11 %49 %105
+ OpStore %55 %54
+ %58 = OpIMul %10 %13 %105
+ %60 = OpAccessChain %11 %56 %105
+ %61 = OpLoad %10 %60
+ %62 = OpAccessChain %11 %56 %58
+ OpStore %62 %61
+ OpBranch %19
+ %19 = OpLabel
+ %64 = OpIAdd %10 %105 %41
+ OpStore %14 %64
+ OpBranch %16
+ %18 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %65 = OpVariable %11 Function
+ %66 = OpVariable %11 Function
+ %74 = OpVariable %28 Function
+ %80 = OpVariable %37 Function
+ %87 = OpVariable %48 Function
+ %94 = OpVariable %48 Function
+ OpStore %65 %13
+ OpStore %66 %22
+ OpBranch %67
+ %67 = OpLabel
+ %106 = OpPhi %10 %22 %9 %102 %70
+ OpLoopMerge %69 %70 None
+ OpBranch %71
+ %71 = OpLabel
+ %73 = OpSGreaterThan %23 %106 %15
+ OpBranchConditional %73 %68 %69
+ %68 = OpLabel
+ %77 = OpAccessChain %11 %74 %106
+ %78 = OpLoad %10 %77
+ %79 = OpAccessChain %11 %74 %106
+ OpStore %79 %78
+ %83 = OpIAdd %10 %106 %41
+ %84 = OpAccessChain %11 %80 %83
+ %85 = OpLoad %10 %84
+ %86 = OpAccessChain %11 %80 %106
+ OpStore %86 %85
+ %90 = OpISub %10 %106 %41
+ %91 = OpAccessChain %11 %87 %90
+ %92 = OpLoad %10 %91
+ %93 = OpAccessChain %11 %87 %106
+ OpStore %93 %92
+ %96 = OpIMul %10 %13 %106
+ %98 = OpAccessChain %11 %94 %106
+ %99 = OpLoad %10 %98
+ %100 = OpAccessChain %11 %94 %96
+ OpStore %100 %99
+ OpBranch %70
+ %70 = OpLabel
+ %102 = OpISub %10 %106 %41
+ OpStore %66 %102
+ OpBranch %67
+ %69 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ // For the loop in function a.
+ {
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 17)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // = dependence
+ // 33 -> 34 tests looking at SIV in same array.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(33), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::EQ);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
+ }
+
+ // > -1 dependence
+ // 44 -> 45 tests looking at SIV in same array with addition.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(44), store[1], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::GT);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, -1);
+ }
+
+ // < 1 dependence
+ // 54 -> 55 tests looking at SIV in same array with subtraction.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(54), store[2], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::LT);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 1);
+ }
+
+ // <=> dependence
+ // 61 -> 62 tests looking at SIV in same array with multiplication.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(61), store[3], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::UNKNOWN);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::ALL);
+ }
+ }
+ // For the loop in function b.
+ {
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 68)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // = dependence
+ // 78 -> 79 tests looking at SIV in same array.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(78), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::EQ);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
+ }
+
+ // < 1 dependence
+ // 85 -> 86 tests looking at SIV in same array with addition.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(85), store[1], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::LT);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 1);
+ }
+
+ // > -1 dependence
+ // 92 -> 93 tests looking at SIV in same array with subtraction.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(92), store[2], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::GT);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, -1);
+ }
+
+ // <=> dependence
+ // 99 -> 100 tests looking at SIV in same array with multiplication.
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(99), store[3], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::UNKNOWN);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::ALL);
+ }
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+layout(location = 0) in vec4 c;
+void a() {
+ int[13] arr;
+ int[15] arr2;
+ int[18] arr3;
+ int[18] arr4;
+ int N = int(c.x);
+ int C = 2;
+ int a = 2;
+ for (int i = 0; i < N; i++) { // Bounds are N - 1
+ arr[i+2*N] = arr[i+N]; // |distance| = N
+ arr2[i+N] = arr2[i+2*N] + C; // |distance| = N
+ arr3[2*i+2*N+1] = arr3[2*i+N+1]; // |distance| = N
+ arr4[a*i+N+1] = arr4[a*i+2*N+1]; // |distance| = N
+ }
+}
+void b() {
+ int[13] arr;
+ int[15] arr2;
+ int[18] arr3;
+ int[18] arr4;
+ int N = int(c.x);
+ int C = 2;
+ int a = 2;
+ for (int i = N; i > 0; i--) { // Bounds are N - 1
+ arr[i+2*N] = arr[i+N]; // |distance| = N
+ arr2[i+N] = arr2[i+2*N] + C; // |distance| = N
+ arr3[2*i+2*N+1] = arr3[2*i+N+1]; // |distance| = N
+ arr4[a*i+N+1] = arr4[a*i+2*N+1]; // |distance| = N
+ }
+}
+void main(){
+ a();
+ b();
+}*/
+TEST(DependencyAnalysis, SymbolicSIV) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %16
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %12 "N"
+ OpName %16 "c"
+ OpName %23 "C"
+ OpName %25 "a"
+ OpName %26 "i"
+ OpName %40 "arr"
+ OpName %54 "arr2"
+ OpName %70 "arr3"
+ OpName %86 "arr4"
+ OpName %105 "N"
+ OpName %109 "C"
+ OpName %110 "a"
+ OpName %111 "i"
+ OpName %120 "arr"
+ OpName %131 "arr2"
+ OpName %144 "arr3"
+ OpName %159 "arr4"
+ OpDecorate %16 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %13 = OpTypeFloat 32
+ %14 = OpTypeVector %13 4
+ %15 = OpTypePointer Input %14
+ %16 = OpVariable %15 Input
+ %17 = OpTypeInt 32 0
+ %18 = OpConstant %17 0
+ %19 = OpTypePointer Input %13
+ %24 = OpConstant %10 2
+ %27 = OpConstant %10 0
+ %35 = OpTypeBool
+ %37 = OpConstant %17 13
+ %38 = OpTypeArray %10 %37
+ %39 = OpTypePointer Function %38
+ %51 = OpConstant %17 15
+ %52 = OpTypeArray %10 %51
+ %53 = OpTypePointer Function %52
+ %67 = OpConstant %17 18
+ %68 = OpTypeArray %10 %67
+ %69 = OpTypePointer Function %68
+ %76 = OpConstant %10 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %178 = OpFunctionCall %2 %6
+ %179 = OpFunctionCall %2 %8
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %12 = OpVariable %11 Function
+ %23 = OpVariable %11 Function
+ %25 = OpVariable %11 Function
+ %26 = OpVariable %11 Function
+ %40 = OpVariable %39 Function
+ %54 = OpVariable %53 Function
+ %70 = OpVariable %69 Function
+ %86 = OpVariable %69 Function
+ %20 = OpAccessChain %19 %16 %18
+ %21 = OpLoad %13 %20
+ %22 = OpConvertFToS %10 %21
+ OpStore %12 %22
+ OpStore %23 %24
+ OpStore %25 %24
+ OpStore %26 %27
+ OpBranch %28
+ %28 = OpLabel
+ %180 = OpPhi %10 %27 %7 %104 %31
+ OpLoopMerge %30 %31 None
+ OpBranch %32
+ %32 = OpLabel
+ %36 = OpSLessThan %35 %180 %22
+ OpBranchConditional %36 %29 %30
+ %29 = OpLabel
+ %43 = OpIMul %10 %24 %22
+ %44 = OpIAdd %10 %180 %43
+ %47 = OpIAdd %10 %180 %22
+ %48 = OpAccessChain %11 %40 %47
+ %49 = OpLoad %10 %48
+ %50 = OpAccessChain %11 %40 %44
+ OpStore %50 %49
+ %57 = OpIAdd %10 %180 %22
+ %60 = OpIMul %10 %24 %22
+ %61 = OpIAdd %10 %180 %60
+ %62 = OpAccessChain %11 %54 %61
+ %63 = OpLoad %10 %62
+ %65 = OpIAdd %10 %63 %24
+ %66 = OpAccessChain %11 %54 %57
+ OpStore %66 %65
+ %72 = OpIMul %10 %24 %180
+ %74 = OpIMul %10 %24 %22
+ %75 = OpIAdd %10 %72 %74
+ %77 = OpIAdd %10 %75 %76
+ %79 = OpIMul %10 %24 %180
+ %81 = OpIAdd %10 %79 %22
+ %82 = OpIAdd %10 %81 %76
+ %83 = OpAccessChain %11 %70 %82
+ %84 = OpLoad %10 %83
+ %85 = OpAccessChain %11 %70 %77
+ OpStore %85 %84
+ %89 = OpIMul %10 %24 %180
+ %91 = OpIAdd %10 %89 %22
+ %92 = OpIAdd %10 %91 %76
+ %95 = OpIMul %10 %24 %180
+ %97 = OpIMul %10 %24 %22
+ %98 = OpIAdd %10 %95 %97
+ %99 = OpIAdd %10 %98 %76
+ %100 = OpAccessChain %11 %86 %99
+ %101 = OpLoad %10 %100
+ %102 = OpAccessChain %11 %86 %92
+ OpStore %102 %101
+ OpBranch %31
+ %31 = OpLabel
+ %104 = OpIAdd %10 %180 %76
+ OpStore %26 %104
+ OpBranch %28
+ %30 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %105 = OpVariable %11 Function
+ %109 = OpVariable %11 Function
+ %110 = OpVariable %11 Function
+ %111 = OpVariable %11 Function
+ %120 = OpVariable %39 Function
+ %131 = OpVariable %53 Function
+ %144 = OpVariable %69 Function
+ %159 = OpVariable %69 Function
+ %106 = OpAccessChain %19 %16 %18
+ %107 = OpLoad %13 %106
+ %108 = OpConvertFToS %10 %107
+ OpStore %105 %108
+ OpStore %109 %24
+ OpStore %110 %24
+ OpStore %111 %108
+ OpBranch %113
+ %113 = OpLabel
+ %181 = OpPhi %10 %108 %9 %177 %116
+ OpLoopMerge %115 %116 None
+ OpBranch %117
+ %117 = OpLabel
+ %119 = OpSGreaterThan %35 %181 %27
+ OpBranchConditional %119 %114 %115
+ %114 = OpLabel
+ %123 = OpIMul %10 %24 %108
+ %124 = OpIAdd %10 %181 %123
+ %127 = OpIAdd %10 %181 %108
+ %128 = OpAccessChain %11 %120 %127
+ %129 = OpLoad %10 %128
+ %130 = OpAccessChain %11 %120 %124
+ OpStore %130 %129
+ %134 = OpIAdd %10 %181 %108
+ %137 = OpIMul %10 %24 %108
+ %138 = OpIAdd %10 %181 %137
+ %139 = OpAccessChain %11 %131 %138
+ %140 = OpLoad %10 %139
+ %142 = OpIAdd %10 %140 %24
+ %143 = OpAccessChain %11 %131 %134
+ OpStore %143 %142
+ %146 = OpIMul %10 %24 %181
+ %148 = OpIMul %10 %24 %108
+ %149 = OpIAdd %10 %146 %148
+ %150 = OpIAdd %10 %149 %76
+ %152 = OpIMul %10 %24 %181
+ %154 = OpIAdd %10 %152 %108
+ %155 = OpIAdd %10 %154 %76
+ %156 = OpAccessChain %11 %144 %155
+ %157 = OpLoad %10 %156
+ %158 = OpAccessChain %11 %144 %150
+ OpStore %158 %157
+ %162 = OpIMul %10 %24 %181
+ %164 = OpIAdd %10 %162 %108
+ %165 = OpIAdd %10 %164 %76
+ %168 = OpIMul %10 %24 %181
+ %170 = OpIMul %10 %24 %108
+ %171 = OpIAdd %10 %168 %170
+ %172 = OpIAdd %10 %171 %76
+ %173 = OpAccessChain %11 %159 %172
+ %174 = OpLoad %10 %173
+ %175 = OpAccessChain %11 %159 %165
+ OpStore %175 %174
+ OpBranch %116
+ %116 = OpLabel
+ %177 = OpISub %10 %181 %76
+ OpStore %111 %177
+ OpBranch %113
+ %115 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ // For the loop in function a.
+ {
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 29)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // independent due to loop bounds (won't enter when N <= 0)
+ // 49 -> 50 tests looking through SIV and symbols with multiplication
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent but not yet supported.
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(49), store[0], &distance_vector));
+ }
+
+ // 63 -> 66 tests looking through SIV and symbols with multiplication and +
+ // C
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent.
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(63),
+ store[1], &distance_vector));
+ }
+
+ // 84 -> 85 tests looking through arithmetic on SIV and symbols
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent but not yet supported.
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(84), store[2], &distance_vector));
+ }
+
+ // 101 -> 102 tests looking through symbol arithmetic on SIV and symbols
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent.
+ EXPECT_TRUE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(101), store[3], &distance_vector));
+ }
+ }
+ // For the loop in function b.
+ {
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 114)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // independent due to loop bounds (won't enter when N <= 0).
+ // 129 -> 130 tests looking through SIV and symbols with multiplication.
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent but not yet supported.
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(129), store[0], &distance_vector));
+ }
+
+ // 140 -> 143 tests looking through SIV and symbols with multiplication and
+ // + C.
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent.
+ EXPECT_TRUE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(140), store[1], &distance_vector));
+ }
+
+ // 157 -> 158 tests looking through arithmetic on SIV and symbols.
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent but not yet supported.
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(157), store[2], &distance_vector));
+ }
+
+ // 174 -> 175 tests looking through symbol arithmetic on SIV and symbols.
+ {
+ DistanceVector distance_vector{loops.size()};
+ // Independent.
+ EXPECT_TRUE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(174), store[3], &distance_vector));
+ }
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void a() {
+ int[6] arr;
+ int N = 5;
+ for (int i = 1; i < N; i++) {
+ arr[i] = arr[N-i];
+ }
+}
+void b() {
+ int[6] arr;
+ int N = 5;
+ for (int i = 1; i < N; i++) {
+ arr[N-i] = arr[i];
+ }
+}
+void c() {
+ int[11] arr;
+ int N = 10;
+ for (int i = 1; i < N; i++) {
+ arr[i] = arr[N-i+1];
+ }
+}
+void d() {
+ int[11] arr;
+ int N = 10;
+ for (int i = 1; i < N; i++) {
+ arr[N-i+1] = arr[i];
+ }
+}
+void e() {
+ int[6] arr;
+ int N = 5;
+ for (int i = N; i > 0; i--) {
+ arr[i] = arr[N-i];
+ }
+}
+void f() {
+ int[6] arr;
+ int N = 5;
+ for (int i = N; i > 0; i--) {
+ arr[N-i] = arr[i];
+ }
+}
+void g() {
+ int[11] arr;
+ int N = 10;
+ for (int i = N; i > 0; i--) {
+ arr[i] = arr[N-i+1];
+ }
+}
+void h() {
+ int[11] arr;
+ int N = 10;
+ for (int i = N; i > 0; i--) {
+ arr[N-i+1] = arr[i];
+ }
+}
+void main(){
+ a();
+ b();
+ c();
+ d();
+ e();
+ f();
+ g();
+ h();
+}
+*/
+TEST(DependencyAnalysis, Crossing) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %10 "c("
+ OpName %12 "d("
+ OpName %14 "e("
+ OpName %16 "f("
+ OpName %18 "g("
+ OpName %20 "h("
+ OpName %24 "N"
+ OpName %26 "i"
+ OpName %41 "arr"
+ OpName %51 "N"
+ OpName %52 "i"
+ OpName %61 "arr"
+ OpName %71 "N"
+ OpName %73 "i"
+ OpName %85 "arr"
+ OpName %96 "N"
+ OpName %97 "i"
+ OpName %106 "arr"
+ OpName %117 "N"
+ OpName %118 "i"
+ OpName %128 "arr"
+ OpName %138 "N"
+ OpName %139 "i"
+ OpName %148 "arr"
+ OpName %158 "N"
+ OpName %159 "i"
+ OpName %168 "arr"
+ OpName %179 "N"
+ OpName %180 "i"
+ OpName %189 "arr"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %22 = OpTypeInt 32 1
+ %23 = OpTypePointer Function %22
+ %25 = OpConstant %22 5
+ %27 = OpConstant %22 1
+ %35 = OpTypeBool
+ %37 = OpTypeInt 32 0
+ %38 = OpConstant %37 6
+ %39 = OpTypeArray %22 %38
+ %40 = OpTypePointer Function %39
+ %72 = OpConstant %22 10
+ %82 = OpConstant %37 11
+ %83 = OpTypeArray %22 %82
+ %84 = OpTypePointer Function %83
+ %126 = OpConstant %22 0
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %200 = OpFunctionCall %2 %6
+ %201 = OpFunctionCall %2 %8
+ %202 = OpFunctionCall %2 %10
+ %203 = OpFunctionCall %2 %12
+ %204 = OpFunctionCall %2 %14
+ %205 = OpFunctionCall %2 %16
+ %206 = OpFunctionCall %2 %18
+ %207 = OpFunctionCall %2 %20
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %24 = OpVariable %23 Function
+ %26 = OpVariable %23 Function
+ %41 = OpVariable %40 Function
+ OpStore %24 %25
+ OpStore %26 %27
+ OpBranch %28
+ %28 = OpLabel
+ %208 = OpPhi %22 %27 %7 %50 %31
+ OpLoopMerge %30 %31 None
+ OpBranch %32
+ %32 = OpLabel
+ %36 = OpSLessThan %35 %208 %25
+ OpBranchConditional %36 %29 %30
+ %29 = OpLabel
+ %45 = OpISub %22 %25 %208
+ %46 = OpAccessChain %23 %41 %45
+ %47 = OpLoad %22 %46
+ %48 = OpAccessChain %23 %41 %208
+ OpStore %48 %47
+ OpBranch %31
+ %31 = OpLabel
+ %50 = OpIAdd %22 %208 %27
+ OpStore %26 %50
+ OpBranch %28
+ %30 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %51 = OpVariable %23 Function
+ %52 = OpVariable %23 Function
+ %61 = OpVariable %40 Function
+ OpStore %51 %25
+ OpStore %52 %27
+ OpBranch %53
+ %53 = OpLabel
+ %209 = OpPhi %22 %27 %9 %70 %56
+ OpLoopMerge %55 %56 None
+ OpBranch %57
+ %57 = OpLabel
+ %60 = OpSLessThan %35 %209 %25
+ OpBranchConditional %60 %54 %55
+ %54 = OpLabel
+ %64 = OpISub %22 %25 %209
+ %66 = OpAccessChain %23 %61 %209
+ %67 = OpLoad %22 %66
+ %68 = OpAccessChain %23 %61 %64
+ OpStore %68 %67
+ OpBranch %56
+ %56 = OpLabel
+ %70 = OpIAdd %22 %209 %27
+ OpStore %52 %70
+ OpBranch %53
+ %55 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %71 = OpVariable %23 Function
+ %73 = OpVariable %23 Function
+ %85 = OpVariable %84 Function
+ OpStore %71 %72
+ OpStore %73 %27
+ OpBranch %74
+ %74 = OpLabel
+ %210 = OpPhi %22 %27 %11 %95 %77
+ OpLoopMerge %76 %77 None
+ OpBranch %78
+ %78 = OpLabel
+ %81 = OpSLessThan %35 %210 %72
+ OpBranchConditional %81 %75 %76
+ %75 = OpLabel
+ %89 = OpISub %22 %72 %210
+ %90 = OpIAdd %22 %89 %27
+ %91 = OpAccessChain %23 %85 %90
+ %92 = OpLoad %22 %91
+ %93 = OpAccessChain %23 %85 %210
+ OpStore %93 %92
+ OpBranch %77
+ %77 = OpLabel
+ %95 = OpIAdd %22 %210 %27
+ OpStore %73 %95
+ OpBranch %74
+ %76 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %96 = OpVariable %23 Function
+ %97 = OpVariable %23 Function
+ %106 = OpVariable %84 Function
+ OpStore %96 %72
+ OpStore %97 %27
+ OpBranch %98
+ %98 = OpLabel
+ %211 = OpPhi %22 %27 %13 %116 %101
+ OpLoopMerge %100 %101 None
+ OpBranch %102
+ %102 = OpLabel
+ %105 = OpSLessThan %35 %211 %72
+ OpBranchConditional %105 %99 %100
+ %99 = OpLabel
+ %109 = OpISub %22 %72 %211
+ %110 = OpIAdd %22 %109 %27
+ %112 = OpAccessChain %23 %106 %211
+ %113 = OpLoad %22 %112
+ %114 = OpAccessChain %23 %106 %110
+ OpStore %114 %113
+ OpBranch %101
+ %101 = OpLabel
+ %116 = OpIAdd %22 %211 %27
+ OpStore %97 %116
+ OpBranch %98
+ %100 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %14 = OpFunction %2 None %3
+ %15 = OpLabel
+ %117 = OpVariable %23 Function
+ %118 = OpVariable %23 Function
+ %128 = OpVariable %40 Function
+ OpStore %117 %25
+ OpStore %118 %25
+ OpBranch %120
+ %120 = OpLabel
+ %212 = OpPhi %22 %25 %15 %137 %123
+ OpLoopMerge %122 %123 None
+ OpBranch %124
+ %124 = OpLabel
+ %127 = OpSGreaterThan %35 %212 %126
+ OpBranchConditional %127 %121 %122
+ %121 = OpLabel
+ %132 = OpISub %22 %25 %212
+ %133 = OpAccessChain %23 %128 %132
+ %134 = OpLoad %22 %133
+ %135 = OpAccessChain %23 %128 %212
+ OpStore %135 %134
+ OpBranch %123
+ %123 = OpLabel
+ %137 = OpISub %22 %212 %27
+ OpStore %118 %137
+ OpBranch %120
+ %122 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %16 = OpFunction %2 None %3
+ %17 = OpLabel
+ %138 = OpVariable %23 Function
+ %139 = OpVariable %23 Function
+ %148 = OpVariable %40 Function
+ OpStore %138 %25
+ OpStore %139 %25
+ OpBranch %141
+ %141 = OpLabel
+ %213 = OpPhi %22 %25 %17 %157 %144
+ OpLoopMerge %143 %144 None
+ OpBranch %145
+ %145 = OpLabel
+ %147 = OpSGreaterThan %35 %213 %126
+ OpBranchConditional %147 %142 %143
+ %142 = OpLabel
+ %151 = OpISub %22 %25 %213
+ %153 = OpAccessChain %23 %148 %213
+ %154 = OpLoad %22 %153
+ %155 = OpAccessChain %23 %148 %151
+ OpStore %155 %154
+ OpBranch %144
+ %144 = OpLabel
+ %157 = OpISub %22 %213 %27
+ OpStore %139 %157
+ OpBranch %141
+ %143 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %18 = OpFunction %2 None %3
+ %19 = OpLabel
+ %158 = OpVariable %23 Function
+ %159 = OpVariable %23 Function
+ %168 = OpVariable %84 Function
+ OpStore %158 %72
+ OpStore %159 %72
+ OpBranch %161
+ %161 = OpLabel
+ %214 = OpPhi %22 %72 %19 %178 %164
+ OpLoopMerge %163 %164 None
+ OpBranch %165
+ %165 = OpLabel
+ %167 = OpSGreaterThan %35 %214 %126
+ OpBranchConditional %167 %162 %163
+ %162 = OpLabel
+ %172 = OpISub %22 %72 %214
+ %173 = OpIAdd %22 %172 %27
+ %174 = OpAccessChain %23 %168 %173
+ %175 = OpLoad %22 %174
+ %176 = OpAccessChain %23 %168 %214
+ OpStore %176 %175
+ OpBranch %164
+ %164 = OpLabel
+ %178 = OpISub %22 %214 %27
+ OpStore %159 %178
+ OpBranch %161
+ %163 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %20 = OpFunction %2 None %3
+ %21 = OpLabel
+ %179 = OpVariable %23 Function
+ %180 = OpVariable %23 Function
+ %189 = OpVariable %84 Function
+ OpStore %179 %72
+ OpStore %180 %72
+ OpBranch %182
+ %182 = OpLabel
+ %215 = OpPhi %22 %72 %21 %199 %185
+ OpLoopMerge %184 %185 None
+ OpBranch %186
+ %186 = OpLabel
+ %188 = OpSGreaterThan %35 %215 %126
+ OpBranchConditional %188 %183 %184
+ %183 = OpLabel
+ %192 = OpISub %22 %72 %215
+ %193 = OpIAdd %22 %192 %27
+ %195 = OpAccessChain %23 %189 %215
+ %196 = OpLoad %22 %195
+ %197 = OpAccessChain %23 %189 %193
+ OpStore %197 %196
+ OpBranch %185
+ %185 = OpLabel
+ %199 = OpISub %22 %215 %27
+ OpStore %180 %199
+ OpBranch %182
+ %184 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ // First two tests can be split into two loops.
+ // Tests even crossing subscripts from low to high indexes.
+ // 47 -> 48
+ {
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 29)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(47),
+ store, &distance_vector));
+ }
+
+ // Tests even crossing subscripts from high to low indexes.
+ // 67 -> 68
+ {
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 54)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(67),
+ store, &distance_vector));
+ }
+
+ // Next two tests can have an end peeled, then be split.
+ // Tests uneven crossing subscripts from low to high indexes.
+ // 92 -> 93
+ {
+ const Function* f = spvtest::GetFunction(module, 10);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 75)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(92),
+ store, &distance_vector));
+ }
+
+ // Tests uneven crossing subscripts from high to low indexes.
+ // 113 -> 114
+ {
+ const Function* f = spvtest::GetFunction(module, 12);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 99)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(113),
+ store, &distance_vector));
+ }
+
+ // First two tests can be split into two loops.
+ // Tests even crossing subscripts from low to high indexes.
+ // 134 -> 135
+ {
+ const Function* f = spvtest::GetFunction(module, 14);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 121)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(134),
+ store, &distance_vector));
+ }
+
+ // Tests even crossing subscripts from high to low indexes.
+ // 154 -> 155
+ {
+ const Function* f = spvtest::GetFunction(module, 16);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 142)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(154),
+ store, &distance_vector));
+ }
+
+ // Next two tests can have an end peeled, then be split.
+ // Tests uneven crossing subscripts from low to high indexes.
+ // 175 -> 176
+ {
+ const Function* f = spvtest::GetFunction(module, 18);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 162)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(175),
+ store, &distance_vector));
+ }
+
+ // Tests uneven crossing subscripts from high to low indexes.
+ // 196 -> 197
+ {
+ const Function* f = spvtest::GetFunction(module, 20);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 183)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ }
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(196),
+ store, &distance_vector));
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void a() {
+ int[10] arr;
+ for (int i = 0; i < 10; i++) {
+ arr[0] = arr[i]; // peel first
+ arr[i] = arr[0]; // peel first
+ arr[9] = arr[i]; // peel last
+ arr[i] = arr[9]; // peel last
+ }
+}
+void b() {
+ int[11] arr;
+ for (int i = 0; i <= 10; i++) {
+ arr[0] = arr[i]; // peel first
+ arr[i] = arr[0]; // peel first
+ arr[10] = arr[i]; // peel last
+ arr[i] = arr[10]; // peel last
+
+ }
+}
+void c() {
+ int[11] arr;
+ for (int i = 10; i > 0; i--) {
+ arr[10] = arr[i]; // peel first
+ arr[i] = arr[10]; // peel first
+ arr[1] = arr[i]; // peel last
+ arr[i] = arr[1]; // peel last
+
+ }
+}
+void d() {
+ int[11] arr;
+ for (int i = 10; i >= 0; i--) {
+ arr[10] = arr[i]; // peel first
+ arr[i] = arr[10]; // peel first
+ arr[0] = arr[i]; // peel last
+ arr[i] = arr[0]; // peel last
+
+ }
+}
+void main(){
+ a();
+ b();
+ c();
+ d();
+}
+*/
+TEST(DependencyAnalysis, WeakZeroSIV) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %10 "c("
+ OpName %12 "d("
+ OpName %16 "i"
+ OpName %31 "arr"
+ OpName %52 "i"
+ OpName %63 "arr"
+ OpName %82 "i"
+ OpName %90 "arr"
+ OpName %109 "i"
+ OpName %117 "arr"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %17 = OpConstant %14 0
+ %24 = OpConstant %14 10
+ %25 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %14 %28
+ %30 = OpTypePointer Function %29
+ %40 = OpConstant %14 9
+ %50 = OpConstant %14 1
+ %60 = OpConstant %27 11
+ %61 = OpTypeArray %14 %60
+ %62 = OpTypePointer Function %61
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %136 = OpFunctionCall %2 %6
+ %137 = OpFunctionCall %2 %8
+ %138 = OpFunctionCall %2 %10
+ %139 = OpFunctionCall %2 %12
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %16 = OpVariable %15 Function
+ %31 = OpVariable %30 Function
+ OpStore %16 %17
+ OpBranch %18
+ %18 = OpLabel
+ %140 = OpPhi %14 %17 %7 %51 %21
+ OpLoopMerge %20 %21 None
+ OpBranch %22
+ %22 = OpLabel
+ %26 = OpSLessThan %25 %140 %24
+ OpBranchConditional %26 %19 %20
+ %19 = OpLabel
+ %33 = OpAccessChain %15 %31 %140
+ %34 = OpLoad %14 %33
+ %35 = OpAccessChain %15 %31 %17
+ OpStore %35 %34
+ %37 = OpAccessChain %15 %31 %17
+ %38 = OpLoad %14 %37
+ %39 = OpAccessChain %15 %31 %140
+ OpStore %39 %38
+ %42 = OpAccessChain %15 %31 %140
+ %43 = OpLoad %14 %42
+ %44 = OpAccessChain %15 %31 %40
+ OpStore %44 %43
+ %46 = OpAccessChain %15 %31 %40
+ %47 = OpLoad %14 %46
+ %48 = OpAccessChain %15 %31 %140
+ OpStore %48 %47
+ OpBranch %21
+ %21 = OpLabel
+ %51 = OpIAdd %14 %140 %50
+ OpStore %16 %51
+ OpBranch %18
+ %20 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %52 = OpVariable %15 Function
+ %63 = OpVariable %62 Function
+ OpStore %52 %17
+ OpBranch %53
+ %53 = OpLabel
+ %141 = OpPhi %14 %17 %9 %81 %56
+ OpLoopMerge %55 %56 None
+ OpBranch %57
+ %57 = OpLabel
+ %59 = OpSLessThanEqual %25 %141 %24
+ OpBranchConditional %59 %54 %55
+ %54 = OpLabel
+ %65 = OpAccessChain %15 %63 %141
+ %66 = OpLoad %14 %65
+ %67 = OpAccessChain %15 %63 %17
+ OpStore %67 %66
+ %69 = OpAccessChain %15 %63 %17
+ %70 = OpLoad %14 %69
+ %71 = OpAccessChain %15 %63 %141
+ OpStore %71 %70
+ %73 = OpAccessChain %15 %63 %141
+ %74 = OpLoad %14 %73
+ %75 = OpAccessChain %15 %63 %24
+ OpStore %75 %74
+ %77 = OpAccessChain %15 %63 %24
+ %78 = OpLoad %14 %77
+ %79 = OpAccessChain %15 %63 %141
+ OpStore %79 %78
+ OpBranch %56
+ %56 = OpLabel
+ %81 = OpIAdd %14 %141 %50
+ OpStore %52 %81
+ OpBranch %53
+ %55 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %82 = OpVariable %15 Function
+ %90 = OpVariable %62 Function
+ OpStore %82 %24
+ OpBranch %83
+ %83 = OpLabel
+ %142 = OpPhi %14 %24 %11 %108 %86
+ OpLoopMerge %85 %86 None
+ OpBranch %87
+ %87 = OpLabel
+ %89 = OpSGreaterThan %25 %142 %17
+ OpBranchConditional %89 %84 %85
+ %84 = OpLabel
+ %92 = OpAccessChain %15 %90 %142
+ %93 = OpLoad %14 %92
+ %94 = OpAccessChain %15 %90 %24
+ OpStore %94 %93
+ %96 = OpAccessChain %15 %90 %24
+ %97 = OpLoad %14 %96
+ %98 = OpAccessChain %15 %90 %142
+ OpStore %98 %97
+ %100 = OpAccessChain %15 %90 %142
+ %101 = OpLoad %14 %100
+ %102 = OpAccessChain %15 %90 %50
+ OpStore %102 %101
+ %104 = OpAccessChain %15 %90 %50
+ %105 = OpLoad %14 %104
+ %106 = OpAccessChain %15 %90 %142
+ OpStore %106 %105
+ OpBranch %86
+ %86 = OpLabel
+ %108 = OpISub %14 %142 %50
+ OpStore %82 %108
+ OpBranch %83
+ %85 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %109 = OpVariable %15 Function
+ %117 = OpVariable %62 Function
+ OpStore %109 %24
+ OpBranch %110
+ %110 = OpLabel
+ %143 = OpPhi %14 %24 %13 %135 %113
+ OpLoopMerge %112 %113 None
+ OpBranch %114
+ %114 = OpLabel
+ %116 = OpSGreaterThanEqual %25 %143 %17
+ OpBranchConditional %116 %111 %112
+ %111 = OpLabel
+ %119 = OpAccessChain %15 %117 %143
+ %120 = OpLoad %14 %119
+ %121 = OpAccessChain %15 %117 %24
+ OpStore %121 %120
+ %123 = OpAccessChain %15 %117 %24
+ %124 = OpLoad %14 %123
+ %125 = OpAccessChain %15 %117 %143
+ OpStore %125 %124
+ %127 = OpAccessChain %15 %117 %143
+ %128 = OpLoad %14 %127
+ %129 = OpAccessChain %15 %117 %17
+ OpStore %129 %128
+ %131 = OpAccessChain %15 %117 %17
+ %132 = OpLoad %14 %131
+ %133 = OpAccessChain %15 %117 %143
+ OpStore %133 %132
+ OpBranch %113
+ %113 = OpLabel
+ %135 = OpISub %14 %143 %50
+ OpStore %109 %135
+ OpBranch %110
+ %112 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ // For the loop in function a
+ {
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 19)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 34 -> 35
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(34), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 38 -> 39
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(38), store[1], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 43 -> 44
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(43), store[2], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 47 -> 48
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(47), store[3], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+ }
+ // For the loop in function b
+ {
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 54)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 66 -> 67
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(66), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 70 -> 71
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(70), store[1], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 74 -> 75
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(74), store[2], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 78 -> 79
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(78), store[3], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+ }
+ // For the loop in function c
+ {
+ const Function* f = spvtest::GetFunction(module, 10);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 84)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 93 -> 94
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(93), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 97 -> 98
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(97), store[1], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 101 -> 102
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(101), store[2], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 105 -> 106
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(105), store[3], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+ }
+ // For the loop in function d
+ {
+ const Function* f = spvtest::GetFunction(module, 12);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[4];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 111)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 4; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 120 -> 121
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(120), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 124 -> 125
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(124), store[1], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
+ }
+
+ // Tests identifying peel first with weak zero with destination as zero
+ // index.
+ // 128 -> 129
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(128), store[2], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+
+ // Tests identifying peel first with weak zero with source as zero index.
+ // 132 -> 133
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(132), store[3], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::PEEL);
+ EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
+ }
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void main(){
+ int[10][10] arr;
+ for (int i = 0; i < 10; i++) {
+ arr[i][i] = arr[i][i];
+ arr[0][i] = arr[1][i];
+ arr[1][i] = arr[0][i];
+ arr[i][0] = arr[i][1];
+ arr[i][1] = arr[i][0];
+ arr[0][1] = arr[1][0];
+ }
+}
+*/
+TEST(DependencyAnalysis, MultipleSubscriptZIVSIV) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %24 "arr"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypeArray %21 %20
+ %23 = OpTypePointer Function %22
+ %33 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %24 = OpVariable %23 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %58 = OpPhi %6 %9 %5 %57 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %58 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %29 = OpAccessChain %7 %24 %58 %58
+ %30 = OpLoad %6 %29
+ %31 = OpAccessChain %7 %24 %58 %58
+ OpStore %31 %30
+ %35 = OpAccessChain %7 %24 %33 %58
+ %36 = OpLoad %6 %35
+ %37 = OpAccessChain %7 %24 %9 %58
+ OpStore %37 %36
+ %40 = OpAccessChain %7 %24 %9 %58
+ %41 = OpLoad %6 %40
+ %42 = OpAccessChain %7 %24 %33 %58
+ OpStore %42 %41
+ %45 = OpAccessChain %7 %24 %58 %33
+ %46 = OpLoad %6 %45
+ %47 = OpAccessChain %7 %24 %58 %9
+ OpStore %47 %46
+ %50 = OpAccessChain %7 %24 %58 %9
+ %51 = OpLoad %6 %50
+ %52 = OpAccessChain %7 %24 %58 %33
+ OpStore %52 %51
+ %53 = OpAccessChain %7 %24 %33 %9
+ %54 = OpLoad %6 %53
+ %55 = OpAccessChain %7 %24 %9 %33
+ OpStore %55 %54
+ OpBranch %13
+ %13 = OpLabel
+ %57 = OpIAdd %6 %58 %33
+ OpStore %8 %57
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[6];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 11)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 6; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // 30 -> 31
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(30),
+ store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::EQ);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
+ }
+
+ // 36 -> 37
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(36),
+ store[1], &distance_vector));
+ }
+
+ // 41 -> 42
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(41),
+ store[2], &distance_vector));
+ }
+
+ // 46 -> 47
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(46),
+ store[3], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::EQ);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
+ }
+
+ // 51 -> 52
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(51),
+ store[4], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::EQ);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
+ }
+
+ // 54 -> 55
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(54),
+ store[5], &distance_vector));
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void a(){
+ int[10] arr;
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ arr[j] = arr[j];
+ }
+ }
+}
+void b(){
+ int[10] arr;
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ arr[i] = arr[i];
+ }
+ }
+}
+void main() {
+ a();
+ b();
+}
+*/
+TEST(DependencyAnalysis, IrrelevantSubscripts) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %12 "i"
+ OpName %23 "j"
+ OpName %35 "arr"
+ OpName %46 "i"
+ OpName %54 "j"
+ OpName %62 "arr"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %13 = OpConstant %10 0
+ %20 = OpConstant %10 10
+ %21 = OpTypeBool
+ %31 = OpTypeInt 32 0
+ %32 = OpConstant %31 10
+ %33 = OpTypeArray %10 %32
+ %34 = OpTypePointer Function %33
+ %42 = OpConstant %10 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %72 = OpFunctionCall %2 %6
+ %73 = OpFunctionCall %2 %8
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %12 = OpVariable %11 Function
+ %23 = OpVariable %11 Function
+ %35 = OpVariable %34 Function
+ OpStore %12 %13
+ OpBranch %14
+ %14 = OpLabel
+ %74 = OpPhi %10 %13 %7 %45 %17
+ OpLoopMerge %16 %17 None
+ OpBranch %18
+ %18 = OpLabel
+ %22 = OpSLessThan %21 %74 %20
+ OpBranchConditional %22 %15 %16
+ %15 = OpLabel
+ OpStore %23 %13
+ OpBranch %24
+ %24 = OpLabel
+ %75 = OpPhi %10 %13 %15 %43 %27
+ OpLoopMerge %26 %27 None
+ OpBranch %28
+ %28 = OpLabel
+ %30 = OpSLessThan %21 %75 %20
+ OpBranchConditional %30 %25 %26
+ %25 = OpLabel
+ %38 = OpAccessChain %11 %35 %75
+ %39 = OpLoad %10 %38
+ %40 = OpAccessChain %11 %35 %75
+ OpStore %40 %39
+ OpBranch %27
+ %27 = OpLabel
+ %43 = OpIAdd %10 %75 %42
+ OpStore %23 %43
+ OpBranch %24
+ %26 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ %45 = OpIAdd %10 %74 %42
+ OpStore %12 %45
+ OpBranch %14
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %46 = OpVariable %11 Function
+ %54 = OpVariable %11 Function
+ %62 = OpVariable %34 Function
+ OpStore %46 %13
+ OpBranch %47
+ %47 = OpLabel
+ %77 = OpPhi %10 %13 %9 %71 %50
+ OpLoopMerge %49 %50 None
+ OpBranch %51
+ %51 = OpLabel
+ %53 = OpSLessThan %21 %77 %20
+ OpBranchConditional %53 %48 %49
+ %48 = OpLabel
+ OpStore %54 %13
+ OpBranch %55
+ %55 = OpLabel
+ %78 = OpPhi %10 %13 %48 %69 %58
+ OpLoopMerge %57 %58 None
+ OpBranch %59
+ %59 = OpLabel
+ %61 = OpSLessThan %21 %78 %20
+ OpBranchConditional %61 %56 %57
+ %56 = OpLabel
+ %65 = OpAccessChain %11 %62 %77
+ %66 = OpLoad %10 %65
+ %67 = OpAccessChain %11 %62 %77
+ OpStore %67 %66
+ OpBranch %58
+ %58 = OpLabel
+ %69 = OpIAdd %10 %78 %42
+ OpStore %54 %69
+ OpBranch %55
+ %57 = OpLabel
+ OpBranch %50
+ %50 = OpLabel
+ %71 = OpIAdd %10 %77 %42
+ OpStore %46 %71
+ OpBranch %47
+ %49 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ // For the loop in function a
+ {
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ std::vector<const Loop*> loops{&ld.GetLoopByIndex(1),
+ &ld.GetLoopByIndex(0)};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[1];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 25)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 1; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // 39 -> 40
+ {
+ DistanceVector distance_vector{loops.size()};
+ analysis.SetDebugStream(std::cout);
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(39), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::IRRELEVANT);
+ EXPECT_EQ(distance_vector.GetEntries()[1].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[1].distance, 0);
+ }
+ }
+
+ // For the loop in function b
+ {
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ std::vector<const Loop*> loops{&ld.GetLoopByIndex(1),
+ &ld.GetLoopByIndex(0)};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[1];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 56)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 1; ++i) {
+ EXPECT_TRUE(store[i]);
+ }
+
+ // 66 -> 67
+ {
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.GetDependence(
+ context->get_def_use_mgr()->GetDef(66), store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::DISTANCE);
+ EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
+ EXPECT_EQ(distance_vector.GetEntries()[1].dependence_information,
+ DistanceEntry::DependenceInformation::IRRELEVANT);
+ }
+ }
+}
+
+void CheckDependenceAndDirection(const Instruction* source,
+ const Instruction* destination,
+ bool expected_dependence,
+ DistanceVector expected_distance,
+ LoopDependenceAnalysis* analysis) {
+ DistanceVector dv_entry(2);
+ EXPECT_EQ(expected_dependence,
+ analysis->GetDependence(source, destination, &dv_entry));
+ EXPECT_EQ(expected_distance, dv_entry);
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+layout(location = 0) in vec4 c;
+void main(){
+ int[10] arr;
+ int a = 2;
+ int b = 3;
+ int N = int(c.x);
+ for (int i = 0; i < 10; i++) {
+ for (int j = 2; j < 10; j++) {
+ arr[i] = arr[j]; // 0
+ arr[j] = arr[i]; // 1
+ arr[j-2] = arr[i+3]; // 2
+ arr[j-a] = arr[i+b]; // 3
+ arr[2*i] = arr[4*j+3]; // 4, independent
+ arr[2*i] = arr[4*j]; // 5
+ arr[i+j] = arr[i+j]; // 6
+ arr[10*i+j] = arr[10*i+j]; // 7
+ arr[10*i+10*j] = arr[10*i+10*j+3]; // 8, independent
+ arr[10*i+10*j] = arr[10*i+N*j+3]; // 9, bail out because of N coefficient
+ arr[10*i+10*j] = arr[10*i+10*j+N]; // 10, bail out because of N constant
+ // term
+ arr[10*i+N*j] = arr[10*i+10*j+3]; // 11, bail out because of N coefficient
+ arr[10*i+10*j+N] = arr[10*i+10*j]; // 12, bail out because of N constant
+ // term
+ arr[10*i] = arr[5*j]; // 13, independent
+ arr[5*i] = arr[10*j]; // 14, independent
+ arr[9*i] = arr[3*j]; // 15, independent
+ arr[3*i] = arr[9*j]; // 16, independent
+ }
+ }
+}
+*/
+TEST(DependencyAnalysis, MIV) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %16
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "a"
+ OpName %10 "b"
+ OpName %12 "N"
+ OpName %16 "c"
+ OpName %23 "i"
+ OpName %34 "j"
+ OpName %45 "arr"
+ OpDecorate %16 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 2
+ %11 = OpConstant %6 3
+ %13 = OpTypeFloat 32
+ %14 = OpTypeVector %13 4
+ %15 = OpTypePointer Input %14
+ %16 = OpVariable %15 Input
+ %17 = OpTypeInt 32 0
+ %18 = OpConstant %17 0
+ %19 = OpTypePointer Input %13
+ %24 = OpConstant %6 0
+ %31 = OpConstant %6 10
+ %32 = OpTypeBool
+ %42 = OpConstant %17 10
+ %43 = OpTypeArray %6 %42
+ %44 = OpTypePointer Function %43
+ %74 = OpConstant %6 4
+ %184 = OpConstant %6 5
+ %197 = OpConstant %6 9
+ %213 = OpConstant %6 1
+ %218 = OpUndef %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %12 = OpVariable %7 Function
+ %23 = OpVariable %7 Function
+ %34 = OpVariable %7 Function
+ %45 = OpVariable %44 Function
+ OpStore %8 %9
+ OpStore %10 %11
+ %20 = OpAccessChain %19 %16 %18
+ %21 = OpLoad %13 %20
+ %22 = OpConvertFToS %6 %21
+ OpStore %12 %22
+ OpStore %23 %24
+ OpBranch %25
+ %25 = OpLabel
+ %217 = OpPhi %6 %24 %5 %216 %28
+ %219 = OpPhi %6 %218 %5 %220 %28
+ OpLoopMerge %27 %28 None
+ OpBranch %29
+ %29 = OpLabel
+ %33 = OpSLessThan %32 %217 %31
+ OpBranchConditional %33 %26 %27
+ %26 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %220 = OpPhi %6 %9 %26 %214 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %32 %220 %31
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %48 = OpAccessChain %7 %45 %220
+ %49 = OpLoad %6 %48
+ %50 = OpAccessChain %7 %45 %217
+ OpStore %50 %49
+ %53 = OpAccessChain %7 %45 %217
+ %54 = OpLoad %6 %53
+ %55 = OpAccessChain %7 %45 %220
+ OpStore %55 %54
+ %57 = OpISub %6 %220 %9
+ %59 = OpIAdd %6 %217 %11
+ %60 = OpAccessChain %7 %45 %59
+ %61 = OpLoad %6 %60
+ %62 = OpAccessChain %7 %45 %57
+ OpStore %62 %61
+ %65 = OpISub %6 %220 %9
+ %68 = OpIAdd %6 %217 %11
+ %69 = OpAccessChain %7 %45 %68
+ %70 = OpLoad %6 %69
+ %71 = OpAccessChain %7 %45 %65
+ OpStore %71 %70
+ %73 = OpIMul %6 %9 %217
+ %76 = OpIMul %6 %74 %220
+ %77 = OpIAdd %6 %76 %11
+ %78 = OpAccessChain %7 %45 %77
+ %79 = OpLoad %6 %78
+ %80 = OpAccessChain %7 %45 %73
+ OpStore %80 %79
+ %82 = OpIMul %6 %9 %217
+ %84 = OpIMul %6 %74 %220
+ %85 = OpAccessChain %7 %45 %84
+ %86 = OpLoad %6 %85
+ %87 = OpAccessChain %7 %45 %82
+ OpStore %87 %86
+ %90 = OpIAdd %6 %217 %220
+ %93 = OpIAdd %6 %217 %220
+ %94 = OpAccessChain %7 %45 %93
+ %95 = OpLoad %6 %94
+ %96 = OpAccessChain %7 %45 %90
+ OpStore %96 %95
+ %98 = OpIMul %6 %31 %217
+ %100 = OpIAdd %6 %98 %220
+ %102 = OpIMul %6 %31 %217
+ %104 = OpIAdd %6 %102 %220
+ %105 = OpAccessChain %7 %45 %104
+ %106 = OpLoad %6 %105
+ %107 = OpAccessChain %7 %45 %100
+ OpStore %107 %106
+ %109 = OpIMul %6 %31 %217
+ %111 = OpIMul %6 %31 %220
+ %112 = OpIAdd %6 %109 %111
+ %114 = OpIMul %6 %31 %217
+ %116 = OpIMul %6 %31 %220
+ %117 = OpIAdd %6 %114 %116
+ %118 = OpIAdd %6 %117 %11
+ %119 = OpAccessChain %7 %45 %118
+ %120 = OpLoad %6 %119
+ %121 = OpAccessChain %7 %45 %112
+ OpStore %121 %120
+ %123 = OpIMul %6 %31 %217
+ %125 = OpIMul %6 %31 %220
+ %126 = OpIAdd %6 %123 %125
+ %128 = OpIMul %6 %31 %217
+ %131 = OpIMul %6 %22 %220
+ %132 = OpIAdd %6 %128 %131
+ %133 = OpIAdd %6 %132 %11
+ %134 = OpAccessChain %7 %45 %133
+ %135 = OpLoad %6 %134
+ %136 = OpAccessChain %7 %45 %126
+ OpStore %136 %135
+ %138 = OpIMul %6 %31 %217
+ %140 = OpIMul %6 %31 %220
+ %141 = OpIAdd %6 %138 %140
+ %143 = OpIMul %6 %31 %217
+ %145 = OpIMul %6 %31 %220
+ %146 = OpIAdd %6 %143 %145
+ %148 = OpIAdd %6 %146 %22
+ %149 = OpAccessChain %7 %45 %148
+ %150 = OpLoad %6 %149
+ %151 = OpAccessChain %7 %45 %141
+ OpStore %151 %150
+ %153 = OpIMul %6 %31 %217
+ %156 = OpIMul %6 %22 %220
+ %157 = OpIAdd %6 %153 %156
+ %159 = OpIMul %6 %31 %217
+ %161 = OpIMul %6 %31 %220
+ %162 = OpIAdd %6 %159 %161
+ %163 = OpIAdd %6 %162 %11
+ %164 = OpAccessChain %7 %45 %163
+ %165 = OpLoad %6 %164
+ %166 = OpAccessChain %7 %45 %157
+ OpStore %166 %165
+ %168 = OpIMul %6 %31 %217
+ %170 = OpIMul %6 %31 %220
+ %171 = OpIAdd %6 %168 %170
+ %173 = OpIAdd %6 %171 %22
+ %175 = OpIMul %6 %31 %217
+ %177 = OpIMul %6 %31 %220
+ %178 = OpIAdd %6 %175 %177
+ %179 = OpAccessChain %7 %45 %178
+ %180 = OpLoad %6 %179
+ %181 = OpAccessChain %7 %45 %173
+ OpStore %181 %180
+ %183 = OpIMul %6 %31 %217
+ %186 = OpIMul %6 %184 %220
+ %187 = OpAccessChain %7 %45 %186
+ %188 = OpLoad %6 %187
+ %189 = OpAccessChain %7 %45 %183
+ OpStore %189 %188
+ %191 = OpIMul %6 %184 %217
+ %193 = OpIMul %6 %31 %220
+ %194 = OpAccessChain %7 %45 %193
+ %195 = OpLoad %6 %194
+ %196 = OpAccessChain %7 %45 %191
+ OpStore %196 %195
+ %199 = OpIMul %6 %197 %217
+ %201 = OpIMul %6 %11 %220
+ %202 = OpAccessChain %7 %45 %201
+ %203 = OpLoad %6 %202
+ %204 = OpAccessChain %7 %45 %199
+ OpStore %204 %203
+ %206 = OpIMul %6 %11 %217
+ %208 = OpIMul %6 %197 %220
+ %209 = OpAccessChain %7 %45 %208
+ %210 = OpLoad %6 %209
+ %211 = OpAccessChain %7 %45 %206
+ OpStore %211 %210
+ OpBranch %38
+ %38 = OpLabel
+ %214 = OpIAdd %6 %220 %213
+ OpStore %34 %214
+ OpBranch %35
+ %37 = OpLabel
+ OpBranch %28
+ %28 = OpLabel
+ %216 = OpIAdd %6 %217 %213
+ OpStore %23 %216
+ OpBranch %25
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ std::vector<const Loop*> loops{&ld.GetLoopByIndex(0), &ld.GetLoopByIndex(1)};
+
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const int instructions_expected = 17;
+ const Instruction* store[instructions_expected];
+ const Instruction* load[instructions_expected];
+ int stores_found = 0;
+ int loads_found = 0;
+
+ int block_id = 36;
+ ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load[loads_found] = &inst;
+ ++loads_found;
+ }
+ }
+
+ EXPECT_EQ(instructions_expected, stores_found);
+ EXPECT_EQ(instructions_expected, loads_found);
+
+ auto directions_all = DistanceEntry(DistanceEntry::Directions::ALL);
+ auto directions_none = DistanceEntry(DistanceEntry::Directions::NONE);
+
+ auto dependent = DistanceVector({directions_all, directions_all});
+ auto independent = DistanceVector({directions_none, directions_none});
+
+ CheckDependenceAndDirection(load[0], store[0], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[1], store[1], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[2], store[2], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[3], store[3], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[4], store[4], true, independent, &analysis);
+ CheckDependenceAndDirection(load[5], store[5], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[6], store[6], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[7], store[7], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[8], store[8], true, independent, &analysis);
+ CheckDependenceAndDirection(load[9], store[9], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[10], store[10], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[11], store[11], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[12], store[12], false, dependent, &analysis);
+ CheckDependenceAndDirection(load[13], store[13], true, independent,
+ &analysis);
+ CheckDependenceAndDirection(load[14], store[14], true, independent,
+ &analysis);
+ CheckDependenceAndDirection(load[15], store[15], true, independent,
+ &analysis);
+ CheckDependenceAndDirection(load[16], store[16], true, independent,
+ &analysis);
+}
+
+void PartitionSubscripts(const Instruction* instruction_0,
+ const Instruction* instruction_1,
+ LoopDependenceAnalysis* analysis,
+ std::vector<std::vector<int>> expected_ids) {
+ auto subscripts_0 = analysis->GetSubscripts(instruction_0);
+ auto subscripts_1 = analysis->GetSubscripts(instruction_1);
+
+ std::vector<std::set<std::pair<Instruction*, Instruction*>>>
+ expected_partition{};
+
+ for (const auto& partition : expected_ids) {
+ expected_partition.push_back(
+ std::set<std::pair<Instruction*, Instruction*>>{});
+ for (auto id : partition) {
+ expected_partition.back().insert({subscripts_0[id], subscripts_1[id]});
+ }
+ }
+
+ EXPECT_EQ(expected_partition,
+ analysis->PartitionSubscripts(subscripts_0, subscripts_1));
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void main(){
+ int[10][10][10][10] arr;
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ for (int k = 0; k < 10; k++) {
+ for (int l = 0; l < 10; l++) {
+ arr[i][j][k][l] = arr[i][j][k][l]; // 0, all independent
+ arr[i][j][k][l] = arr[i][j][l][0]; // 1, last 2 coupled
+ arr[i][j][k][l] = arr[j][i][k][l]; // 2, first 2 coupled
+ arr[i][j][k][l] = arr[l][j][k][i]; // 3, first & last coupled
+ arr[i][j][k][l] = arr[i][k][j][l]; // 4, middle 2 coupled
+ arr[i+j][j][k][l] = arr[i][j][k][l]; // 5, first 2 coupled
+ arr[i+j+k][j][k][l] = arr[i][j][k][l]; // 6, first 3 coupled
+ arr[i+j+k+l][j][k][l] = arr[i][j][k][l]; // 7, all 4 coupled
+ arr[i][j][k][l] = arr[i][l][j][k]; // 8, last 3 coupled
+ arr[i][j-k][k][l] = arr[i][j][l][k]; // 9, last 3 coupled
+ arr[i][j][k][l] = arr[l][i][j][k]; // 10, all 4 coupled
+ arr[i][j][k][l] = arr[j][i][l][k]; // 11, 2 coupled partitions (i,j) &
+(l&k)
+ arr[i][j][k][l] = arr[k][l][i][j]; // 12, 2 coupled partitions (i,k) &
+(j&l)
+ }
+ }
+ }
+ }
+}
+*/
+TEST(DependencyAnalysis, SubscriptPartitioning) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %27 "k"
+ OpName %35 "l"
+ OpName %50 "arr"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %43 = OpTypeInt 32 0
+ %44 = OpConstant %43 10
+ %45 = OpTypeArray %6 %44
+ %46 = OpTypeArray %45 %44
+ %47 = OpTypeArray %46 %44
+ %48 = OpTypeArray %47 %44
+ %49 = OpTypePointer Function %48
+ %208 = OpConstant %6 1
+ %217 = OpUndef %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %27 = OpVariable %7 Function
+ %35 = OpVariable %7 Function
+ %50 = OpVariable %49 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %216 = OpPhi %6 %9 %5 %215 %13
+ %218 = OpPhi %6 %217 %5 %221 %13
+ %219 = OpPhi %6 %217 %5 %222 %13
+ %220 = OpPhi %6 %217 %5 %223 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %216 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %221 = OpPhi %6 %9 %11 %213 %23
+ %222 = OpPhi %6 %219 %11 %224 %23
+ %223 = OpPhi %6 %220 %11 %225 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %221 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ OpStore %27 %9
+ OpBranch %28
+ %28 = OpLabel
+ %224 = OpPhi %6 %9 %21 %211 %31
+ %225 = OpPhi %6 %223 %21 %226 %31
+ OpLoopMerge %30 %31 None
+ OpBranch %32
+ %32 = OpLabel
+ %34 = OpSLessThan %17 %224 %16
+ OpBranchConditional %34 %29 %30
+ %29 = OpLabel
+ OpStore %35 %9
+ OpBranch %36
+ %36 = OpLabel
+ %226 = OpPhi %6 %9 %29 %209 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %17 %226 %16
+ OpBranchConditional %42 %37 %38
+ %37 = OpLabel
+ %59 = OpAccessChain %7 %50 %216 %221 %224 %226
+ %60 = OpLoad %6 %59
+ %61 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %61 %60
+ %69 = OpAccessChain %7 %50 %216 %221 %226 %9
+ %70 = OpLoad %6 %69
+ %71 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %71 %70
+ %80 = OpAccessChain %7 %50 %221 %216 %224 %226
+ %81 = OpLoad %6 %80
+ %82 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %82 %81
+ %91 = OpAccessChain %7 %50 %226 %221 %224 %216
+ %92 = OpLoad %6 %91
+ %93 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %93 %92
+ %102 = OpAccessChain %7 %50 %216 %224 %221 %226
+ %103 = OpLoad %6 %102
+ %104 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %104 %103
+ %107 = OpIAdd %6 %216 %221
+ %115 = OpAccessChain %7 %50 %216 %221 %224 %226
+ %116 = OpLoad %6 %115
+ %117 = OpAccessChain %7 %50 %107 %221 %224 %226
+ OpStore %117 %116
+ %120 = OpIAdd %6 %216 %221
+ %122 = OpIAdd %6 %120 %224
+ %130 = OpAccessChain %7 %50 %216 %221 %224 %226
+ %131 = OpLoad %6 %130
+ %132 = OpAccessChain %7 %50 %122 %221 %224 %226
+ OpStore %132 %131
+ %135 = OpIAdd %6 %216 %221
+ %137 = OpIAdd %6 %135 %224
+ %139 = OpIAdd %6 %137 %226
+ %147 = OpAccessChain %7 %50 %216 %221 %224 %226
+ %148 = OpLoad %6 %147
+ %149 = OpAccessChain %7 %50 %139 %221 %224 %226
+ OpStore %149 %148
+ %158 = OpAccessChain %7 %50 %216 %226 %221 %224
+ %159 = OpLoad %6 %158
+ %160 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %160 %159
+ %164 = OpISub %6 %221 %224
+ %171 = OpAccessChain %7 %50 %216 %221 %226 %224
+ %172 = OpLoad %6 %171
+ %173 = OpAccessChain %7 %50 %216 %164 %224 %226
+ OpStore %173 %172
+ %182 = OpAccessChain %7 %50 %226 %216 %221 %224
+ %183 = OpLoad %6 %182
+ %184 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %184 %183
+ %193 = OpAccessChain %7 %50 %221 %216 %226 %224
+ %194 = OpLoad %6 %193
+ %195 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %195 %194
+ %204 = OpAccessChain %7 %50 %224 %226 %216 %221
+ %205 = OpLoad %6 %204
+ %206 = OpAccessChain %7 %50 %216 %221 %224 %226
+ OpStore %206 %205
+ OpBranch %39
+ %39 = OpLabel
+ %209 = OpIAdd %6 %226 %208
+ OpStore %35 %209
+ OpBranch %36
+ %38 = OpLabel
+ OpBranch %31
+ %31 = OpLabel
+ %211 = OpIAdd %6 %224 %208
+ OpStore %27 %211
+ OpBranch %28
+ %30 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %213 = OpIAdd %6 %221 %208
+ OpStore %19 %213
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %215 = OpIAdd %6 %216 %208
+ OpStore %8 %215
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ std::vector<const Loop*> loop_nest{
+ &ld.GetLoopByIndex(0), &ld.GetLoopByIndex(1), &ld.GetLoopByIndex(2),
+ &ld.GetLoopByIndex(3)};
+ LoopDependenceAnalysis analysis{context.get(), loop_nest};
+
+ const int instructions_expected = 13;
+ const Instruction* store[instructions_expected];
+ const Instruction* load[instructions_expected];
+ int stores_found = 0;
+ int loads_found = 0;
+
+ int block_id = 37;
+ ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load[loads_found] = &inst;
+ ++loads_found;
+ }
+ }
+
+ EXPECT_EQ(instructions_expected, stores_found);
+ EXPECT_EQ(instructions_expected, loads_found);
+
+ PartitionSubscripts(load[0], store[0], &analysis, {{0}, {1}, {2}, {3}});
+ PartitionSubscripts(load[1], store[1], &analysis, {{0}, {1}, {2, 3}});
+ PartitionSubscripts(load[2], store[2], &analysis, {{0, 1}, {2}, {3}});
+ PartitionSubscripts(load[3], store[3], &analysis, {{0, 3}, {1}, {2}});
+ PartitionSubscripts(load[4], store[4], &analysis, {{0}, {1, 2}, {3}});
+ PartitionSubscripts(load[5], store[5], &analysis, {{0, 1}, {2}, {3}});
+ PartitionSubscripts(load[6], store[6], &analysis, {{0, 1, 2}, {3}});
+ PartitionSubscripts(load[7], store[7], &analysis, {{0, 1, 2, 3}});
+ PartitionSubscripts(load[8], store[8], &analysis, {{0}, {1, 2, 3}});
+ PartitionSubscripts(load[9], store[9], &analysis, {{0}, {1, 2, 3}});
+ PartitionSubscripts(load[10], store[10], &analysis, {{0, 1, 2, 3}});
+ PartitionSubscripts(load[11], store[11], &analysis, {{0, 1}, {2, 3}});
+ PartitionSubscripts(load[12], store[12], &analysis, {{0, 2}, {1, 3}});
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+
+#version 440 core
+void a() {
+ int[10][10] arr;
+ for (int i = 0; i < 10; ++i) {
+ for (int j = 0; j < 10; ++j) {
+ // Dependent, distance vector (1, -1)
+ arr[i+1][i+j] = arr[i][i+j];
+ }
+ }
+}
+
+void b() {
+ int[10][10] arr;
+ for (int i = 0; i < 10; ++i) {
+ // Independent
+ arr[i+1][i+2] = arr[i][i] + 2;
+ }
+}
+
+void c() {
+ int[10][10] arr;
+ for (int i = 0; i < 10; ++i) {
+ // Dependence point (1,2)
+ arr[i][i] = arr[1][i-1] + 2;
+ }
+}
+
+void d() {
+ int[10][10][10] arr;
+ for (int i = 0; i < 10; ++i) {
+ for (int j = 0; j < 10; ++j) {
+ for (int k = 0; k < 10; ++k) {
+ // Dependent, distance vector (1,1,-1)
+ arr[j-i][i+1][j+k] = arr[j-i][i][j+k];
+ }
+ }
+ }
+}
+
+void e() {
+ int[10][10] arr;
+ for (int i = 0; i < 10; ++i) {
+ for (int j = 0; j < 10; ++j) {
+ // Independent with GCD after propagation
+ arr[i][2*j+i] = arr[i][2*j-i+5];
+ }
+ }
+}
+
+void main(){
+ a();
+ b();
+ c();
+ d();
+ e();
+}
+*/
+TEST(DependencyAnalysis, Delta) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %10 "c("
+ OpName %12 "d("
+ OpName %14 "e("
+ OpName %18 "i"
+ OpName %29 "j"
+ OpName %42 "arr"
+ OpName %60 "i"
+ OpName %68 "arr"
+ OpName %82 "i"
+ OpName %90 "arr"
+ OpName %101 "i"
+ OpName %109 "j"
+ OpName %117 "k"
+ OpName %127 "arr"
+ OpName %152 "i"
+ OpName %160 "j"
+ OpName %168 "arr"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %16 = OpTypeInt 32 1
+ %17 = OpTypePointer Function %16
+ %19 = OpConstant %16 0
+ %26 = OpConstant %16 10
+ %27 = OpTypeBool
+ %37 = OpTypeInt 32 0
+ %38 = OpConstant %37 10
+ %39 = OpTypeArray %16 %38
+ %40 = OpTypeArray %39 %38
+ %41 = OpTypePointer Function %40
+ %44 = OpConstant %16 1
+ %72 = OpConstant %16 2
+ %125 = OpTypeArray %40 %38
+ %126 = OpTypePointer Function %125
+ %179 = OpConstant %16 5
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %188 = OpFunctionCall %2 %6
+ %189 = OpFunctionCall %2 %8
+ %190 = OpFunctionCall %2 %10
+ %191 = OpFunctionCall %2 %12
+ %192 = OpFunctionCall %2 %14
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %18 = OpVariable %17 Function
+ %29 = OpVariable %17 Function
+ %42 = OpVariable %41 Function
+ OpStore %18 %19
+ OpBranch %20
+ %20 = OpLabel
+ %193 = OpPhi %16 %19 %7 %59 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %28 = OpSLessThan %27 %193 %26
+ OpBranchConditional %28 %21 %22
+ %21 = OpLabel
+ OpStore %29 %19
+ OpBranch %30
+ %30 = OpLabel
+ %194 = OpPhi %16 %19 %21 %57 %33
+ OpLoopMerge %32 %33 None
+ OpBranch %34
+ %34 = OpLabel
+ %36 = OpSLessThan %27 %194 %26
+ OpBranchConditional %36 %31 %32
+ %31 = OpLabel
+ %45 = OpIAdd %16 %193 %44
+ %48 = OpIAdd %16 %193 %194
+ %52 = OpIAdd %16 %193 %194
+ %53 = OpAccessChain %17 %42 %193 %52
+ %54 = OpLoad %16 %53
+ %55 = OpAccessChain %17 %42 %45 %48
+ OpStore %55 %54
+ OpBranch %33
+ %33 = OpLabel
+ %57 = OpIAdd %16 %194 %44
+ OpStore %29 %57
+ OpBranch %30
+ %32 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %59 = OpIAdd %16 %193 %44
+ OpStore %18 %59
+ OpBranch %20
+ %22 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %60 = OpVariable %17 Function
+ %68 = OpVariable %41 Function
+ OpStore %60 %19
+ OpBranch %61
+ %61 = OpLabel
+ %196 = OpPhi %16 %19 %9 %81 %64
+ OpLoopMerge %63 %64 None
+ OpBranch %65
+ %65 = OpLabel
+ %67 = OpSLessThan %27 %196 %26
+ OpBranchConditional %67 %62 %63
+ %62 = OpLabel
+ %70 = OpIAdd %16 %196 %44
+ %73 = OpIAdd %16 %196 %72
+ %76 = OpAccessChain %17 %68 %196 %196
+ %77 = OpLoad %16 %76
+ %78 = OpIAdd %16 %77 %72
+ %79 = OpAccessChain %17 %68 %70 %73
+ OpStore %79 %78
+ OpBranch %64
+ %64 = OpLabel
+ %81 = OpIAdd %16 %196 %44
+ OpStore %60 %81
+ OpBranch %61
+ %63 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %82 = OpVariable %17 Function
+ %90 = OpVariable %41 Function
+ OpStore %82 %19
+ OpBranch %83
+ %83 = OpLabel
+ %197 = OpPhi %16 %19 %11 %100 %86
+ OpLoopMerge %85 %86 None
+ OpBranch %87
+ %87 = OpLabel
+ %89 = OpSLessThan %27 %197 %26
+ OpBranchConditional %89 %84 %85
+ %84 = OpLabel
+ %94 = OpISub %16 %197 %44
+ %95 = OpAccessChain %17 %90 %44 %94
+ %96 = OpLoad %16 %95
+ %97 = OpIAdd %16 %96 %72
+ %98 = OpAccessChain %17 %90 %197 %197
+ OpStore %98 %97
+ OpBranch %86
+ %86 = OpLabel
+ %100 = OpIAdd %16 %197 %44
+ OpStore %82 %100
+ OpBranch %83
+ %85 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %101 = OpVariable %17 Function
+ %109 = OpVariable %17 Function
+ %117 = OpVariable %17 Function
+ %127 = OpVariable %126 Function
+ OpStore %101 %19
+ OpBranch %102
+ %102 = OpLabel
+ %198 = OpPhi %16 %19 %13 %151 %105
+ OpLoopMerge %104 %105 None
+ OpBranch %106
+ %106 = OpLabel
+ %108 = OpSLessThan %27 %198 %26
+ OpBranchConditional %108 %103 %104
+ %103 = OpLabel
+ OpStore %109 %19
+ OpBranch %110
+ %110 = OpLabel
+ %199 = OpPhi %16 %19 %103 %149 %113
+ OpLoopMerge %112 %113 None
+ OpBranch %114
+ %114 = OpLabel
+ %116 = OpSLessThan %27 %199 %26
+ OpBranchConditional %116 %111 %112
+ %111 = OpLabel
+ OpStore %117 %19
+ OpBranch %118
+ %118 = OpLabel
+ %201 = OpPhi %16 %19 %111 %147 %121
+ OpLoopMerge %120 %121 None
+ OpBranch %122
+ %122 = OpLabel
+ %124 = OpSLessThan %27 %201 %26
+ OpBranchConditional %124 %119 %120
+ %119 = OpLabel
+ %130 = OpISub %16 %199 %198
+ %132 = OpIAdd %16 %198 %44
+ %135 = OpIAdd %16 %199 %201
+ %138 = OpISub %16 %199 %198
+ %142 = OpIAdd %16 %199 %201
+ %143 = OpAccessChain %17 %127 %138 %198 %142
+ %144 = OpLoad %16 %143
+ %145 = OpAccessChain %17 %127 %130 %132 %135
+ OpStore %145 %144
+ OpBranch %121
+ %121 = OpLabel
+ %147 = OpIAdd %16 %201 %44
+ OpStore %117 %147
+ OpBranch %118
+ %120 = OpLabel
+ OpBranch %113
+ %113 = OpLabel
+ %149 = OpIAdd %16 %199 %44
+ OpStore %109 %149
+ OpBranch %110
+ %112 = OpLabel
+ OpBranch %105
+ %105 = OpLabel
+ %151 = OpIAdd %16 %198 %44
+ OpStore %101 %151
+ OpBranch %102
+ %104 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %14 = OpFunction %2 None %3
+ %15 = OpLabel
+ %152 = OpVariable %17 Function
+ %160 = OpVariable %17 Function
+ %168 = OpVariable %41 Function
+ OpStore %152 %19
+ OpBranch %153
+ %153 = OpLabel
+ %204 = OpPhi %16 %19 %15 %187 %156
+ OpLoopMerge %155 %156 None
+ OpBranch %157
+ %157 = OpLabel
+ %159 = OpSLessThan %27 %204 %26
+ OpBranchConditional %159 %154 %155
+ %154 = OpLabel
+ OpStore %160 %19
+ OpBranch %161
+ %161 = OpLabel
+ %205 = OpPhi %16 %19 %154 %185 %164
+ OpLoopMerge %163 %164 None
+ OpBranch %165
+ %165 = OpLabel
+ %167 = OpSLessThan %27 %205 %26
+ OpBranchConditional %167 %162 %163
+ %162 = OpLabel
+ %171 = OpIMul %16 %72 %205
+ %173 = OpIAdd %16 %171 %204
+ %176 = OpIMul %16 %72 %205
+ %178 = OpISub %16 %176 %204
+ %180 = OpIAdd %16 %178 %179
+ %181 = OpAccessChain %17 %168 %204 %180
+ %182 = OpLoad %16 %181
+ %183 = OpAccessChain %17 %168 %204 %173
+ OpStore %183 %182
+ OpBranch %164
+ %164 = OpLabel
+ %185 = OpIAdd %16 %205 %44
+ OpStore %160 %185
+ OpBranch %161
+ %163 = OpLabel
+ OpBranch %156
+ %156 = OpLabel
+ %187 = OpIAdd %16 %204 %44
+ OpStore %152 %187
+ OpBranch %153
+ %155 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ ASSERT_NE(nullptr, context);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ {
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ const Instruction* store = nullptr;
+ const Instruction* load = nullptr;
+
+ int block_id = 31;
+ ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(nullptr, store);
+ EXPECT_NE(nullptr, load);
+
+ std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0),
+ &ld.GetLoopByIndex(1)};
+ LoopDependenceAnalysis analysis{context.get(), loop_nest};
+
+ DistanceVector dv_entry(loop_nest.size());
+
+ std::vector<DistanceEntry> expected_entries{
+ DistanceEntry(DistanceEntry::Directions::LT, 1),
+ DistanceEntry(DistanceEntry::Directions::LT, 1)};
+
+ DistanceVector expected_distance_vector(expected_entries);
+
+ auto is_independent = analysis.GetDependence(load, store, &dv_entry);
+
+ EXPECT_FALSE(is_independent);
+ EXPECT_EQ(expected_distance_vector, dv_entry);
+ }
+
+ {
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ const Instruction* store = nullptr;
+ const Instruction* load = nullptr;
+
+ int block_id = 62;
+ ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(nullptr, store);
+ EXPECT_NE(nullptr, load);
+
+ std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0)};
+ LoopDependenceAnalysis analysis{context.get(), loop_nest};
+
+ DistanceVector dv_entry(loop_nest.size());
+ auto is_independent = analysis.GetDependence(load, store, &dv_entry);
+
+ EXPECT_TRUE(is_independent);
+ }
+
+ {
+ const Function* f = spvtest::GetFunction(module, 10);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ const Instruction* store = nullptr;
+ const Instruction* load = nullptr;
+
+ int block_id = 84;
+ ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(nullptr, store);
+ EXPECT_NE(nullptr, load);
+
+ std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0)};
+ LoopDependenceAnalysis analysis{context.get(), loop_nest};
+
+ DistanceVector dv_entry(loop_nest.size());
+ auto is_independent = analysis.GetDependence(load, store, &dv_entry);
+
+ DistanceVector expected_distance_vector({DistanceEntry(1, 2)});
+
+ EXPECT_FALSE(is_independent);
+ EXPECT_EQ(expected_distance_vector, dv_entry);
+ }
+
+ {
+ const Function* f = spvtest::GetFunction(module, 12);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ const Instruction* store = nullptr;
+ const Instruction* load = nullptr;
+
+ int block_id = 119;
+ ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(nullptr, store);
+ EXPECT_NE(nullptr, load);
+
+ std::vector<const Loop*> loop_nest{
+ &ld.GetLoopByIndex(0), &ld.GetLoopByIndex(1), &ld.GetLoopByIndex(2)};
+ LoopDependenceAnalysis analysis{context.get(), loop_nest};
+
+ DistanceVector dv_entry(loop_nest.size());
+
+ std::vector<DistanceEntry> expected_entries{
+ DistanceEntry(DistanceEntry::Directions::LT, 1),
+ DistanceEntry(DistanceEntry::Directions::LT, 1),
+ DistanceEntry(DistanceEntry::Directions::GT, -1)};
+
+ DistanceVector expected_distance_vector(expected_entries);
+
+ auto is_independent = analysis.GetDependence(store, load, &dv_entry);
+
+ EXPECT_FALSE(is_independent);
+ EXPECT_EQ(expected_distance_vector, dv_entry);
+ }
+
+ {
+ const Function* f = spvtest::GetFunction(module, 14);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ const Instruction* store = nullptr;
+ const Instruction* load = nullptr;
+
+ int block_id = 162;
+ ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(nullptr, store);
+ EXPECT_NE(nullptr, load);
+
+ std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0),
+ &ld.GetLoopByIndex(1)};
+ LoopDependenceAnalysis analysis{context.get(), loop_nest};
+
+ DistanceVector dv_entry(loop_nest.size());
+ auto is_independent = analysis.GetDependence(load, store, &dv_entry);
+
+ EXPECT_TRUE(is_independent);
+ }
+}
+
+TEST(DependencyAnalysis, ConstraintIntersection) {
+ LoopDependenceAnalysis analysis{nullptr, std::vector<const Loop*>{}};
+ auto scalar_evolution = analysis.GetScalarEvolution();
+ {
+ // One is none. Other should be returned
+ auto none = analysis.make_constraint<DependenceNone>();
+ auto x = scalar_evolution->CreateConstant(1);
+ auto y = scalar_evolution->CreateConstant(10);
+ auto point = analysis.make_constraint<DependencePoint>(x, y, nullptr);
+
+ auto ret_0 = analysis.IntersectConstraints(none, point, nullptr, nullptr);
+
+ auto ret_point_0 = ret_0->AsDependencePoint();
+ ASSERT_NE(nullptr, ret_point_0);
+ EXPECT_EQ(*x, *ret_point_0->GetSource());
+ EXPECT_EQ(*y, *ret_point_0->GetDestination());
+
+ auto ret_1 = analysis.IntersectConstraints(point, none, nullptr, nullptr);
+
+ auto ret_point_1 = ret_1->AsDependencePoint();
+ ASSERT_NE(nullptr, ret_point_1);
+ EXPECT_EQ(*x, *ret_point_1->GetSource());
+ EXPECT_EQ(*y, *ret_point_1->GetDestination());
+ }
+
+ {
+ // Both distances
+ auto x = scalar_evolution->CreateConstant(1);
+ auto y = scalar_evolution->CreateConstant(10);
+
+ auto distance_0 = analysis.make_constraint<DependenceDistance>(x, nullptr);
+ auto distance_1 = analysis.make_constraint<DependenceDistance>(y, nullptr);
+
+ // Equal distances
+ auto ret_0 =
+ analysis.IntersectConstraints(distance_1, distance_1, nullptr, nullptr);
+
+ auto ret_distance = ret_0->AsDependenceDistance();
+ ASSERT_NE(nullptr, ret_distance);
+ EXPECT_EQ(*y, *ret_distance->GetDistance());
+
+ // Non-equal distances
+ auto ret_1 =
+ analysis.IntersectConstraints(distance_0, distance_1, nullptr, nullptr);
+ EXPECT_NE(nullptr, ret_1->AsDependenceEmpty());
+ }
+
+ {
+ // Both points
+ auto x = scalar_evolution->CreateConstant(1);
+ auto y = scalar_evolution->CreateConstant(10);
+
+ auto point_0 = analysis.make_constraint<DependencePoint>(x, y, nullptr);
+ auto point_1 = analysis.make_constraint<DependencePoint>(x, y, nullptr);
+ auto point_2 = analysis.make_constraint<DependencePoint>(y, y, nullptr);
+
+ // Equal points
+ auto ret_0 =
+ analysis.IntersectConstraints(point_0, point_1, nullptr, nullptr);
+ auto ret_point_0 = ret_0->AsDependencePoint();
+ ASSERT_NE(nullptr, ret_point_0);
+ EXPECT_EQ(*x, *ret_point_0->GetSource());
+ EXPECT_EQ(*y, *ret_point_0->GetDestination());
+
+ // Non-equal points
+ auto ret_1 =
+ analysis.IntersectConstraints(point_0, point_2, nullptr, nullptr);
+ EXPECT_NE(nullptr, ret_1->AsDependenceEmpty());
+ }
+
+ {
+ // Both lines, parallel
+ auto a0 = scalar_evolution->CreateConstant(3);
+ auto b0 = scalar_evolution->CreateConstant(6);
+ auto c0 = scalar_evolution->CreateConstant(9);
+
+ auto a1 = scalar_evolution->CreateConstant(6);
+ auto b1 = scalar_evolution->CreateConstant(12);
+ auto c1 = scalar_evolution->CreateConstant(18);
+
+ auto line_0 = analysis.make_constraint<DependenceLine>(a0, b0, c0, nullptr);
+ auto line_1 = analysis.make_constraint<DependenceLine>(a1, b1, c1, nullptr);
+
+ // Same line, both ways
+ auto ret_0 =
+ analysis.IntersectConstraints(line_0, line_1, nullptr, nullptr);
+ auto ret_1 =
+ analysis.IntersectConstraints(line_1, line_0, nullptr, nullptr);
+
+ auto ret_line_0 = ret_0->AsDependenceLine();
+ auto ret_line_1 = ret_1->AsDependenceLine();
+
+ EXPECT_NE(nullptr, ret_line_0);
+ EXPECT_NE(nullptr, ret_line_1);
+
+ // Non-intersecting parallel lines
+ auto c2 = scalar_evolution->CreateConstant(12);
+ auto line_2 = analysis.make_constraint<DependenceLine>(a1, b1, c2, nullptr);
+
+ auto ret_2 =
+ analysis.IntersectConstraints(line_0, line_2, nullptr, nullptr);
+ auto ret_3 =
+ analysis.IntersectConstraints(line_2, line_0, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
+ EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());
+
+ auto c3 = scalar_evolution->CreateConstant(20);
+ auto line_3 = analysis.make_constraint<DependenceLine>(a1, b1, c3, nullptr);
+
+ auto ret_4 =
+ analysis.IntersectConstraints(line_0, line_3, nullptr, nullptr);
+ auto ret_5 =
+ analysis.IntersectConstraints(line_3, line_0, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_4->AsDependenceEmpty());
+ EXPECT_NE(nullptr, ret_5->AsDependenceEmpty());
+ }
+
+ {
+ // Non-constant line
+ auto unknown = scalar_evolution->CreateCantComputeNode();
+ auto constant = scalar_evolution->CreateConstant(10);
+
+ auto line_0 = analysis.make_constraint<DependenceLine>(constant, constant,
+ constant, nullptr);
+ auto line_1 = analysis.make_constraint<DependenceLine>(unknown, unknown,
+ unknown, nullptr);
+
+ auto ret_0 =
+ analysis.IntersectConstraints(line_0, line_1, nullptr, nullptr);
+ auto ret_1 =
+ analysis.IntersectConstraints(line_1, line_0, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_0->AsDependenceNone());
+ EXPECT_NE(nullptr, ret_1->AsDependenceNone());
+ }
+
+ {
+ auto bound_0 = scalar_evolution->CreateConstant(0);
+ auto bound_1 = scalar_evolution->CreateConstant(20);
+
+ auto a0 = scalar_evolution->CreateConstant(1);
+ auto b0 = scalar_evolution->CreateConstant(2);
+ auto c0 = scalar_evolution->CreateConstant(6);
+
+ auto a1 = scalar_evolution->CreateConstant(-1);
+ auto b1 = scalar_evolution->CreateConstant(2);
+ auto c1 = scalar_evolution->CreateConstant(2);
+
+ auto line_0 = analysis.make_constraint<DependenceLine>(a0, b0, c0, nullptr);
+ auto line_1 = analysis.make_constraint<DependenceLine>(a1, b1, c1, nullptr);
+
+ // Intersecting lines, has integer solution, in bounds
+ auto ret_0 =
+ analysis.IntersectConstraints(line_0, line_1, bound_0, bound_1);
+ auto ret_1 =
+ analysis.IntersectConstraints(line_1, line_0, bound_0, bound_1);
+
+ auto ret_point_0 = ret_0->AsDependencePoint();
+ auto ret_point_1 = ret_1->AsDependencePoint();
+
+ EXPECT_NE(nullptr, ret_point_0);
+ EXPECT_NE(nullptr, ret_point_1);
+
+ auto const_2 = scalar_evolution->CreateConstant(2);
+
+ EXPECT_EQ(*const_2, *ret_point_0->GetSource());
+ EXPECT_EQ(*const_2, *ret_point_0->GetDestination());
+
+ EXPECT_EQ(*const_2, *ret_point_1->GetSource());
+ EXPECT_EQ(*const_2, *ret_point_1->GetDestination());
+
+ // Intersecting lines, has integer solution, out of bounds
+ auto ret_2 =
+ analysis.IntersectConstraints(line_0, line_1, bound_0, bound_0);
+ auto ret_3 =
+ analysis.IntersectConstraints(line_1, line_0, bound_0, bound_0);
+
+ EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
+ EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());
+
+ auto a2 = scalar_evolution->CreateConstant(-4);
+ auto b2 = scalar_evolution->CreateConstant(1);
+ auto c2 = scalar_evolution->CreateConstant(0);
+
+ auto a3 = scalar_evolution->CreateConstant(4);
+ auto b3 = scalar_evolution->CreateConstant(1);
+ auto c3 = scalar_evolution->CreateConstant(4);
+
+ auto line_2 = analysis.make_constraint<DependenceLine>(a2, b2, c2, nullptr);
+ auto line_3 = analysis.make_constraint<DependenceLine>(a3, b3, c3, nullptr);
+
+ // Intersecting, no integer solution
+ auto ret_4 =
+ analysis.IntersectConstraints(line_2, line_3, bound_0, bound_1);
+ auto ret_5 =
+ analysis.IntersectConstraints(line_3, line_2, bound_0, bound_1);
+
+ EXPECT_NE(nullptr, ret_4->AsDependenceEmpty());
+ EXPECT_NE(nullptr, ret_5->AsDependenceEmpty());
+
+ auto unknown = scalar_evolution->CreateCantComputeNode();
+
+ // Non-constant bound
+ auto ret_6 =
+ analysis.IntersectConstraints(line_0, line_1, unknown, bound_1);
+ auto ret_7 =
+ analysis.IntersectConstraints(line_1, line_0, bound_0, unknown);
+
+ EXPECT_NE(nullptr, ret_6->AsDependenceNone());
+ EXPECT_NE(nullptr, ret_7->AsDependenceNone());
+ }
+
+ {
+ auto constant_0 = scalar_evolution->CreateConstant(0);
+ auto constant_1 = scalar_evolution->CreateConstant(1);
+ auto constant_neg_1 = scalar_evolution->CreateConstant(-1);
+ auto constant_2 = scalar_evolution->CreateConstant(2);
+ auto constant_neg_2 = scalar_evolution->CreateConstant(-2);
+
+ auto point_0_0 = analysis.make_constraint<DependencePoint>(
+ constant_0, constant_0, nullptr);
+ auto point_0_1 = analysis.make_constraint<DependencePoint>(
+ constant_0, constant_1, nullptr);
+ auto point_1_0 = analysis.make_constraint<DependencePoint>(
+ constant_1, constant_0, nullptr);
+ auto point_1_1 = analysis.make_constraint<DependencePoint>(
+ constant_1, constant_1, nullptr);
+ auto point_1_2 = analysis.make_constraint<DependencePoint>(
+ constant_1, constant_2, nullptr);
+ auto point_1_neg_1 = analysis.make_constraint<DependencePoint>(
+ constant_1, constant_neg_1, nullptr);
+ auto point_neg_1_1 = analysis.make_constraint<DependencePoint>(
+ constant_neg_1, constant_1, nullptr);
+
+ auto line_y_0 = analysis.make_constraint<DependenceLine>(
+ constant_0, constant_1, constant_0, nullptr);
+ auto line_y_1 = analysis.make_constraint<DependenceLine>(
+ constant_0, constant_1, constant_1, nullptr);
+ auto line_y_2 = analysis.make_constraint<DependenceLine>(
+ constant_0, constant_1, constant_2, nullptr);
+
+ // Parallel horizontal lines, y = 0 & y = 1, should return no intersection
+ auto ret =
+ analysis.IntersectConstraints(line_y_0, line_y_1, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret->AsDependenceEmpty());
+
+ // Parallel horizontal lines, y = 1 & y = 2, should return no intersection
+ auto ret_y_12 =
+ analysis.IntersectConstraints(line_y_1, line_y_2, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_y_12->AsDependenceEmpty());
+
+ // Same horizontal lines, y = 0 & y = 0, should return the line
+ auto ret_y_same_0 =
+ analysis.IntersectConstraints(line_y_0, line_y_0, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_y_same_0->AsDependenceLine());
+
+ // Same horizontal lines, y = 1 & y = 1, should return the line
+ auto ret_y_same_1 =
+ analysis.IntersectConstraints(line_y_1, line_y_1, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_y_same_1->AsDependenceLine());
+
+ auto line_x_0 = analysis.make_constraint<DependenceLine>(
+ constant_1, constant_0, constant_0, nullptr);
+ auto line_x_1 = analysis.make_constraint<DependenceLine>(
+ constant_1, constant_0, constant_1, nullptr);
+ auto line_x_2 = analysis.make_constraint<DependenceLine>(
+ constant_1, constant_0, constant_2, nullptr);
+ auto line_2x_1 = analysis.make_constraint<DependenceLine>(
+ constant_2, constant_0, constant_1, nullptr);
+ auto line_2x_2 = analysis.make_constraint<DependenceLine>(
+ constant_2, constant_0, constant_2, nullptr);
+
+ // Parallel vertical lines, x = 0 & x = 1, should return no intersection
+ auto ret_x =
+ analysis.IntersectConstraints(line_x_0, line_x_1, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_x->AsDependenceEmpty());
+
+ // Parallel vertical lines, x = 1 & x = 2, should return no intersection
+ auto ret_x_12 =
+ analysis.IntersectConstraints(line_x_1, line_x_2, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_x_12->AsDependenceEmpty());
+
+ // Parallel vertical lines, 2x = 1 & 2x = 2, should return no intersection
+ auto ret_2x_2_2x_1 =
+ analysis.IntersectConstraints(line_2x_2, line_2x_1, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_2x_2_2x_1->AsDependenceEmpty());
+
+ // same line, 2x=2 & x = 1
+ auto ret_2x_2_x_1 =
+ analysis.IntersectConstraints(line_2x_2, line_x_1, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_2x_2_x_1->AsDependenceLine());
+
+ // Same vertical lines, x = 0 & x = 0, should return the line
+ auto ret_x_same_0 =
+ analysis.IntersectConstraints(line_x_0, line_x_0, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_x_same_0->AsDependenceLine());
+ // EXPECT_EQ(*line_x_0, *ret_x_same_0->AsDependenceLine());
+
+ // Same vertical lines, x = 1 & x = 1, should return the line
+ auto ret_x_same_1 =
+ analysis.IntersectConstraints(line_x_1, line_x_1, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_x_same_1->AsDependenceLine());
+ EXPECT_EQ(*line_x_1, *ret_x_same_1->AsDependenceLine());
+
+ // x=1 & y = 0, intersect at (1, 0)
+ auto ret_1_0 = analysis.IntersectConstraints(line_x_1, line_y_0,
+ constant_neg_1, constant_2);
+
+ auto ret_point_1_0 = ret_1_0->AsDependencePoint();
+ EXPECT_NE(nullptr, ret_point_1_0);
+ EXPECT_EQ(*point_1_0, *ret_point_1_0);
+
+ // x=1 & y = 1, intersect at (1, 1)
+ auto ret_1_1 = analysis.IntersectConstraints(line_x_1, line_y_1,
+ constant_neg_1, constant_2);
+
+ auto ret_point_1_1 = ret_1_1->AsDependencePoint();
+ EXPECT_NE(nullptr, ret_point_1_1);
+ EXPECT_EQ(*point_1_1, *ret_point_1_1);
+
+ // x=0 & y = 0, intersect at (0, 0)
+ auto ret_0_0 = analysis.IntersectConstraints(line_x_0, line_y_0,
+ constant_neg_1, constant_2);
+
+ auto ret_point_0_0 = ret_0_0->AsDependencePoint();
+ EXPECT_NE(nullptr, ret_point_0_0);
+ EXPECT_EQ(*point_0_0, *ret_point_0_0);
+
+ // x=0 & y = 1, intersect at (0, 1)
+ auto ret_0_1 = analysis.IntersectConstraints(line_x_0, line_y_1,
+ constant_neg_1, constant_2);
+ auto ret_point_0_1 = ret_0_1->AsDependencePoint();
+ EXPECT_NE(nullptr, ret_point_0_1);
+ EXPECT_EQ(*point_0_1, *ret_point_0_1);
+
+ // x = 1 & y = 2
+ auto ret_1_2 = analysis.IntersectConstraints(line_x_1, line_y_2,
+ constant_neg_1, constant_2);
+ auto ret_point_1_2 = ret_1_2->AsDependencePoint();
+ EXPECT_NE(nullptr, ret_point_1_2);
+ EXPECT_EQ(*point_1_2, *ret_point_1_2);
+
+ auto line_x_y_0 = analysis.make_constraint<DependenceLine>(
+ constant_1, constant_1, constant_0, nullptr);
+ auto line_x_y_1 = analysis.make_constraint<DependenceLine>(
+ constant_1, constant_1, constant_1, nullptr);
+
+ // x+y=0 & x=0, intersect (0, 0)
+ auto ret_xy_0_x_0 = analysis.IntersectConstraints(
+ line_x_y_0, line_x_0, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_0_x_0->AsDependencePoint());
+ EXPECT_EQ(*point_0_0, *ret_xy_0_x_0);
+
+ // x+y=0 & y=0, intersect (0, 0)
+ auto ret_xy_0_y_0 = analysis.IntersectConstraints(
+ line_x_y_0, line_y_0, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_0_y_0->AsDependencePoint());
+ EXPECT_EQ(*point_0_0, *ret_xy_0_y_0);
+
+ // x+y=0 & x=1, intersect (1, -1)
+ auto ret_xy_0_x_1 = analysis.IntersectConstraints(
+ line_x_y_0, line_x_1, constant_neg_2, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_0_x_1->AsDependencePoint());
+ EXPECT_EQ(*point_1_neg_1, *ret_xy_0_x_1);
+
+ // x+y=0 & y=1, intersect (-1, 1)
+ auto ret_xy_0_y_1 = analysis.IntersectConstraints(
+ line_x_y_0, line_y_1, constant_neg_2, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_0_y_1->AsDependencePoint());
+ EXPECT_EQ(*point_neg_1_1, *ret_xy_0_y_1);
+
+ // x=0 & x+y=0, intersect (0, 0)
+ auto ret_x_0_xy_0 = analysis.IntersectConstraints(
+ line_x_0, line_x_y_0, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_x_0_xy_0->AsDependencePoint());
+ EXPECT_EQ(*point_0_0, *ret_x_0_xy_0);
+
+ // y=0 & x+y=0, intersect (0, 0)
+ auto ret_y_0_xy_0 = analysis.IntersectConstraints(
+ line_y_0, line_x_y_0, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_y_0_xy_0->AsDependencePoint());
+ EXPECT_EQ(*point_0_0, *ret_y_0_xy_0);
+
+ // x=1 & x+y=0, intersect (1, -1)
+ auto ret_x_1_xy_0 = analysis.IntersectConstraints(
+ line_x_1, line_x_y_0, constant_neg_2, constant_2);
+
+ EXPECT_NE(nullptr, ret_x_1_xy_0->AsDependencePoint());
+ EXPECT_EQ(*point_1_neg_1, *ret_x_1_xy_0);
+
+ // y=1 & x+y=0, intersect (-1, 1)
+ auto ret_y_1_xy_0 = analysis.IntersectConstraints(
+ line_y_1, line_x_y_0, constant_neg_2, constant_2);
+
+ EXPECT_NE(nullptr, ret_y_1_xy_0->AsDependencePoint());
+ EXPECT_EQ(*point_neg_1_1, *ret_y_1_xy_0);
+
+ // x+y=1 & x=0, intersect (0, 1)
+ auto ret_xy_1_x_0 = analysis.IntersectConstraints(
+ line_x_y_1, line_x_0, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_1_x_0->AsDependencePoint());
+ EXPECT_EQ(*point_0_1, *ret_xy_1_x_0);
+
+ // x+y=1 & y=0, intersect (1, 0)
+ auto ret_xy_1_y_0 = analysis.IntersectConstraints(
+ line_x_y_1, line_y_0, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_1_y_0->AsDependencePoint());
+ EXPECT_EQ(*point_1_0, *ret_xy_1_y_0);
+
+ // x+y=1 & x=1, intersect (1, 0)
+ auto ret_xy_1_x_1 = analysis.IntersectConstraints(
+ line_x_y_1, line_x_1, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_1_x_1->AsDependencePoint());
+ EXPECT_EQ(*point_1_0, *ret_xy_1_x_1);
+
+ // x+y=1 & y=1, intersect (0, 1)
+ auto ret_xy_1_y_1 = analysis.IntersectConstraints(
+ line_x_y_1, line_y_1, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_xy_1_y_1->AsDependencePoint());
+ EXPECT_EQ(*point_0_1, *ret_xy_1_y_1);
+
+ // x=0 & x+y=1, intersect (0, 1)
+ auto ret_x_0_xy_1 = analysis.IntersectConstraints(
+ line_x_0, line_x_y_1, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_x_0_xy_1->AsDependencePoint());
+ EXPECT_EQ(*point_0_1, *ret_x_0_xy_1);
+
+ // y=0 & x+y=1, intersect (1, 0)
+ auto ret_y_0_xy_1 = analysis.IntersectConstraints(
+ line_y_0, line_x_y_1, constant_neg_1, constant_2);
+
+ EXPECT_NE(nullptr, ret_y_0_xy_1->AsDependencePoint());
+ EXPECT_EQ(*point_1_0, *ret_y_0_xy_1);
+
+ // x=1 & x+y=1, intersect (1, 0)
+ auto ret_x_1_xy_1 = analysis.IntersectConstraints(
+ line_x_1, line_x_y_1, constant_neg_2, constant_2);
+
+ EXPECT_NE(nullptr, ret_x_1_xy_1->AsDependencePoint());
+ EXPECT_EQ(*point_1_0, *ret_x_1_xy_1);
+
+ // y=1 & x+y=1, intersect (0, 1)
+ auto ret_y_1_xy_1 = analysis.IntersectConstraints(
+ line_y_1, line_x_y_1, constant_neg_2, constant_2);
+
+ EXPECT_NE(nullptr, ret_y_1_xy_1->AsDependencePoint());
+ EXPECT_EQ(*point_0_1, *ret_y_1_xy_1);
+ }
+
+ {
+ // Line and point
+ auto a = scalar_evolution->CreateConstant(3);
+ auto b = scalar_evolution->CreateConstant(10);
+ auto c = scalar_evolution->CreateConstant(16);
+
+ auto line = analysis.make_constraint<DependenceLine>(a, b, c, nullptr);
+
+ // Point on line
+ auto x = scalar_evolution->CreateConstant(2);
+ auto y = scalar_evolution->CreateConstant(1);
+ auto point_0 = analysis.make_constraint<DependencePoint>(x, y, nullptr);
+
+ auto ret_0 = analysis.IntersectConstraints(line, point_0, nullptr, nullptr);
+ auto ret_1 = analysis.IntersectConstraints(point_0, line, nullptr, nullptr);
+
+ auto ret_point_0 = ret_0->AsDependencePoint();
+ auto ret_point_1 = ret_1->AsDependencePoint();
+ ASSERT_NE(nullptr, ret_point_0);
+ ASSERT_NE(nullptr, ret_point_1);
+
+ EXPECT_EQ(*x, *ret_point_0->GetSource());
+ EXPECT_EQ(*y, *ret_point_0->GetDestination());
+
+ EXPECT_EQ(*x, *ret_point_1->GetSource());
+ EXPECT_EQ(*y, *ret_point_1->GetDestination());
+
+ // Point not on line
+ auto point_1 = analysis.make_constraint<DependencePoint>(a, a, nullptr);
+
+ auto ret_2 = analysis.IntersectConstraints(line, point_1, nullptr, nullptr);
+ auto ret_3 = analysis.IntersectConstraints(point_1, line, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
+ EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());
+
+ // Non-constant
+ auto unknown = scalar_evolution->CreateCantComputeNode();
+
+ auto point_2 =
+ analysis.make_constraint<DependencePoint>(unknown, x, nullptr);
+
+ auto ret_4 = analysis.IntersectConstraints(line, point_2, nullptr, nullptr);
+ auto ret_5 = analysis.IntersectConstraints(point_2, line, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_4->AsDependenceNone());
+ EXPECT_NE(nullptr, ret_5->AsDependenceNone());
+ }
+
+ {
+ // Distance and point
+ auto d = scalar_evolution->CreateConstant(5);
+ auto distance = analysis.make_constraint<DependenceDistance>(d, nullptr);
+
+ // Point on line
+ auto x = scalar_evolution->CreateConstant(10);
+ auto point_0 = analysis.make_constraint<DependencePoint>(d, x, nullptr);
+
+ auto ret_0 =
+ analysis.IntersectConstraints(distance, point_0, nullptr, nullptr);
+ auto ret_1 =
+ analysis.IntersectConstraints(point_0, distance, nullptr, nullptr);
+
+ auto ret_point_0 = ret_0->AsDependencePoint();
+ auto ret_point_1 = ret_1->AsDependencePoint();
+ ASSERT_NE(nullptr, ret_point_0);
+ ASSERT_NE(nullptr, ret_point_1);
+
+ // Point not on line
+ auto point_1 = analysis.make_constraint<DependencePoint>(x, x, nullptr);
+
+ auto ret_2 =
+ analysis.IntersectConstraints(distance, point_1, nullptr, nullptr);
+ auto ret_3 =
+ analysis.IntersectConstraints(point_1, distance, nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
+ EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());
+
+ // Non-constant
+ auto unknown = scalar_evolution->CreateCantComputeNode();
+ auto unknown_distance =
+ analysis.make_constraint<DependenceDistance>(unknown, nullptr);
+
+ auto ret_4 = analysis.IntersectConstraints(unknown_distance, point_1,
+ nullptr, nullptr);
+ auto ret_5 = analysis.IntersectConstraints(point_1, unknown_distance,
+ nullptr, nullptr);
+
+ EXPECT_NE(nullptr, ret_4->AsDependenceNone());
+ EXPECT_NE(nullptr, ret_5->AsDependenceNone());
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/dependence_analysis_helpers.cpp b/test/opt/loop_optimizations/dependence_analysis_helpers.cpp
new file mode 100644
index 0000000..715cf54
--- /dev/null
+++ b/test/opt/loop_optimizations/dependence_analysis_helpers.cpp
@@ -0,0 +1,3017 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/iterator.h"
+#include "source/opt/loop_dependence.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "source/opt/scalar_analysis.h"
+#include "source/opt/tree_iterator.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using DependencyAnalysisHelpers = ::testing::Test;
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void a() {
+ int[10][10] arr;
+ int i = 0;
+ int j = 0;
+ for (; i < 10 && j < 10; i++, j++) {
+ arr[i][j] = arr[i][j];
+ }
+}
+void b() {
+ int[10] arr;
+ for (int i = 0; i < 10; i+=2) {
+ arr[i] = arr[i];
+ }
+}
+void main(){
+ a();
+ b();
+}
+*/
+TEST(DependencyAnalysisHelpers, UnsupportedLoops) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %12 "i"
+ OpName %14 "j"
+ OpName %32 "arr"
+ OpName %45 "i"
+ OpName %54 "arr"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %13 = OpConstant %10 0
+ %21 = OpConstant %10 10
+ %22 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %10 %28
+ %30 = OpTypeArray %29 %28
+ %31 = OpTypePointer Function %30
+ %41 = OpConstant %10 1
+ %53 = OpTypePointer Function %29
+ %60 = OpConstant %10 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %63 = OpFunctionCall %2 %6
+ %64 = OpFunctionCall %2 %8
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %12 = OpVariable %11 Function
+ %14 = OpVariable %11 Function
+ %32 = OpVariable %31 Function
+ OpStore %12 %13
+ OpStore %14 %13
+ OpBranch %15
+ %15 = OpLabel
+ %65 = OpPhi %10 %13 %7 %42 %18
+ %66 = OpPhi %10 %13 %7 %44 %18
+ OpLoopMerge %17 %18 None
+ OpBranch %19
+ %19 = OpLabel
+ %23 = OpSLessThan %22 %65 %21
+ %25 = OpSLessThan %22 %66 %21
+ %26 = OpLogicalAnd %22 %23 %25
+ OpBranchConditional %26 %16 %17
+ %16 = OpLabel
+ %37 = OpAccessChain %11 %32 %65 %66
+ %38 = OpLoad %10 %37
+ %39 = OpAccessChain %11 %32 %65 %66
+ OpStore %39 %38
+ OpBranch %18
+ %18 = OpLabel
+ %42 = OpIAdd %10 %65 %41
+ OpStore %12 %42
+ %44 = OpIAdd %10 %66 %41
+ OpStore %14 %44
+ OpBranch %15
+ %17 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %45 = OpVariable %11 Function
+ %54 = OpVariable %53 Function
+ OpStore %45 %13
+ OpBranch %46
+ %46 = OpLabel
+ %67 = OpPhi %10 %13 %9 %62 %49
+ OpLoopMerge %48 %49 None
+ OpBranch %50
+ %50 = OpLabel
+ %52 = OpSLessThan %22 %67 %21
+ OpBranchConditional %52 %47 %48
+ %47 = OpLabel
+ %57 = OpAccessChain %11 %54 %67
+ %58 = OpLoad %10 %57
+ %59 = OpAccessChain %11 %54 %67
+ OpStore %59 %58
+ OpBranch %49
+ %49 = OpLabel
+ %62 = OpIAdd %10 %67 %60
+ OpStore %45 %62
+ OpBranch %46
+ %48 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ {
+ // Function a
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[1] = {nullptr};
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 16)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+ // 38 -> 39
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.IsSupportedLoop(loops[0]));
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(38),
+ store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::UNKNOWN);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::ALL);
+ }
+ {
+ // Function b
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* store[1] = {nullptr};
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 47)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+ // 58 -> 59
+ DistanceVector distance_vector{loops.size()};
+ EXPECT_FALSE(analysis.IsSupportedLoop(loops[0]));
+ EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(58),
+ store[0], &distance_vector));
+ EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
+ DistanceEntry::DependenceInformation::UNKNOWN);
+ EXPECT_EQ(distance_vector.GetEntries()[0].direction,
+ DistanceEntry::Directions::ALL);
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void a() {
+ for (int i = -10; i < 0; i++) {
+
+ }
+}
+void b() {
+ for (int i = -5; i < 5; i++) {
+
+ }
+}
+void c() {
+ for (int i = 0; i < 10; i++) {
+
+ }
+}
+void d() {
+ for (int i = 5; i < 15; i++) {
+
+ }
+}
+void e() {
+ for (int i = -10; i <= 0; i++) {
+
+ }
+}
+void f() {
+ for (int i = -5; i <= 5; i++) {
+
+ }
+}
+void g() {
+ for (int i = 0; i <= 10; i++) {
+
+ }
+}
+void h() {
+ for (int i = 5; i <= 15; i++) {
+
+ }
+}
+void i() {
+ for (int i = 0; i > -10; i--) {
+
+ }
+}
+void j() {
+ for (int i = 5; i > -5; i--) {
+
+ }
+}
+void k() {
+ for (int i = 10; i > 0; i--) {
+
+ }
+}
+void l() {
+ for (int i = 15; i > 5; i--) {
+
+ }
+}
+void m() {
+ for (int i = 0; i >= -10; i--) {
+
+ }
+}
+void n() {
+ for (int i = 5; i >= -5; i--) {
+
+ }
+}
+void o() {
+ for (int i = 10; i >= 0; i--) {
+
+ }
+}
+void p() {
+ for (int i = 15; i >= 5; i--) {
+
+ }
+}
+void main(){
+ a();
+ b();
+ c();
+ d();
+ e();
+ f();
+ g();
+ h();
+ i();
+ j();
+ k();
+ l();
+ m();
+ n();
+ o();
+ p();
+}
+*/
+TEST(DependencyAnalysisHelpers, loop_information) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %10 "c("
+ OpName %12 "d("
+ OpName %14 "e("
+ OpName %16 "f("
+ OpName %18 "g("
+ OpName %20 "h("
+ OpName %22 "i("
+ OpName %24 "j("
+ OpName %26 "k("
+ OpName %28 "l("
+ OpName %30 "m("
+ OpName %32 "n("
+ OpName %34 "o("
+ OpName %36 "p("
+ OpName %40 "i"
+ OpName %54 "i"
+ OpName %66 "i"
+ OpName %77 "i"
+ OpName %88 "i"
+ OpName %98 "i"
+ OpName %108 "i"
+ OpName %118 "i"
+ OpName %128 "i"
+ OpName %138 "i"
+ OpName %148 "i"
+ OpName %158 "i"
+ OpName %168 "i"
+ OpName %178 "i"
+ OpName %188 "i"
+ OpName %198 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %38 = OpTypeInt 32 1
+ %39 = OpTypePointer Function %38
+ %41 = OpConstant %38 -10
+ %48 = OpConstant %38 0
+ %49 = OpTypeBool
+ %52 = OpConstant %38 1
+ %55 = OpConstant %38 -5
+ %62 = OpConstant %38 5
+ %73 = OpConstant %38 10
+ %84 = OpConstant %38 15
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %208 = OpFunctionCall %2 %6
+ %209 = OpFunctionCall %2 %8
+ %210 = OpFunctionCall %2 %10
+ %211 = OpFunctionCall %2 %12
+ %212 = OpFunctionCall %2 %14
+ %213 = OpFunctionCall %2 %16
+ %214 = OpFunctionCall %2 %18
+ %215 = OpFunctionCall %2 %20
+ %216 = OpFunctionCall %2 %22
+ %217 = OpFunctionCall %2 %24
+ %218 = OpFunctionCall %2 %26
+ %219 = OpFunctionCall %2 %28
+ %220 = OpFunctionCall %2 %30
+ %221 = OpFunctionCall %2 %32
+ %222 = OpFunctionCall %2 %34
+ %223 = OpFunctionCall %2 %36
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %40 = OpVariable %39 Function
+ OpStore %40 %41
+ OpBranch %42
+ %42 = OpLabel
+ %224 = OpPhi %38 %41 %7 %53 %45
+ OpLoopMerge %44 %45 None
+ OpBranch %46
+ %46 = OpLabel
+ %50 = OpSLessThan %49 %224 %48
+ OpBranchConditional %50 %43 %44
+ %43 = OpLabel
+ OpBranch %45
+ %45 = OpLabel
+ %53 = OpIAdd %38 %224 %52
+ OpStore %40 %53
+ OpBranch %42
+ %44 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %54 = OpVariable %39 Function
+ OpStore %54 %55
+ OpBranch %56
+ %56 = OpLabel
+ %225 = OpPhi %38 %55 %9 %65 %59
+ OpLoopMerge %58 %59 None
+ OpBranch %60
+ %60 = OpLabel
+ %63 = OpSLessThan %49 %225 %62
+ OpBranchConditional %63 %57 %58
+ %57 = OpLabel
+ OpBranch %59
+ %59 = OpLabel
+ %65 = OpIAdd %38 %225 %52
+ OpStore %54 %65
+ OpBranch %56
+ %58 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %66 = OpVariable %39 Function
+ OpStore %66 %48
+ OpBranch %67
+ %67 = OpLabel
+ %226 = OpPhi %38 %48 %11 %76 %70
+ OpLoopMerge %69 %70 None
+ OpBranch %71
+ %71 = OpLabel
+ %74 = OpSLessThan %49 %226 %73
+ OpBranchConditional %74 %68 %69
+ %68 = OpLabel
+ OpBranch %70
+ %70 = OpLabel
+ %76 = OpIAdd %38 %226 %52
+ OpStore %66 %76
+ OpBranch %67
+ %69 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %77 = OpVariable %39 Function
+ OpStore %77 %62
+ OpBranch %78
+ %78 = OpLabel
+ %227 = OpPhi %38 %62 %13 %87 %81
+ OpLoopMerge %80 %81 None
+ OpBranch %82
+ %82 = OpLabel
+ %85 = OpSLessThan %49 %227 %84
+ OpBranchConditional %85 %79 %80
+ %79 = OpLabel
+ OpBranch %81
+ %81 = OpLabel
+ %87 = OpIAdd %38 %227 %52
+ OpStore %77 %87
+ OpBranch %78
+ %80 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %14 = OpFunction %2 None %3
+ %15 = OpLabel
+ %88 = OpVariable %39 Function
+ OpStore %88 %41
+ OpBranch %89
+ %89 = OpLabel
+ %228 = OpPhi %38 %41 %15 %97 %92
+ OpLoopMerge %91 %92 None
+ OpBranch %93
+ %93 = OpLabel
+ %95 = OpSLessThanEqual %49 %228 %48
+ OpBranchConditional %95 %90 %91
+ %90 = OpLabel
+ OpBranch %92
+ %92 = OpLabel
+ %97 = OpIAdd %38 %228 %52
+ OpStore %88 %97
+ OpBranch %89
+ %91 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %16 = OpFunction %2 None %3
+ %17 = OpLabel
+ %98 = OpVariable %39 Function
+ OpStore %98 %55
+ OpBranch %99
+ %99 = OpLabel
+ %229 = OpPhi %38 %55 %17 %107 %102
+ OpLoopMerge %101 %102 None
+ OpBranch %103
+ %103 = OpLabel
+ %105 = OpSLessThanEqual %49 %229 %62
+ OpBranchConditional %105 %100 %101
+ %100 = OpLabel
+ OpBranch %102
+ %102 = OpLabel
+ %107 = OpIAdd %38 %229 %52
+ OpStore %98 %107
+ OpBranch %99
+ %101 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %18 = OpFunction %2 None %3
+ %19 = OpLabel
+ %108 = OpVariable %39 Function
+ OpStore %108 %48
+ OpBranch %109
+ %109 = OpLabel
+ %230 = OpPhi %38 %48 %19 %117 %112
+ OpLoopMerge %111 %112 None
+ OpBranch %113
+ %113 = OpLabel
+ %115 = OpSLessThanEqual %49 %230 %73
+ OpBranchConditional %115 %110 %111
+ %110 = OpLabel
+ OpBranch %112
+ %112 = OpLabel
+ %117 = OpIAdd %38 %230 %52
+ OpStore %108 %117
+ OpBranch %109
+ %111 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %20 = OpFunction %2 None %3
+ %21 = OpLabel
+ %118 = OpVariable %39 Function
+ OpStore %118 %62
+ OpBranch %119
+ %119 = OpLabel
+ %231 = OpPhi %38 %62 %21 %127 %122
+ OpLoopMerge %121 %122 None
+ OpBranch %123
+ %123 = OpLabel
+ %125 = OpSLessThanEqual %49 %231 %84
+ OpBranchConditional %125 %120 %121
+ %120 = OpLabel
+ OpBranch %122
+ %122 = OpLabel
+ %127 = OpIAdd %38 %231 %52
+ OpStore %118 %127
+ OpBranch %119
+ %121 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %22 = OpFunction %2 None %3
+ %23 = OpLabel
+ %128 = OpVariable %39 Function
+ OpStore %128 %48
+ OpBranch %129
+ %129 = OpLabel
+ %232 = OpPhi %38 %48 %23 %137 %132
+ OpLoopMerge %131 %132 None
+ OpBranch %133
+ %133 = OpLabel
+ %135 = OpSGreaterThan %49 %232 %41
+ OpBranchConditional %135 %130 %131
+ %130 = OpLabel
+ OpBranch %132
+ %132 = OpLabel
+ %137 = OpISub %38 %232 %52
+ OpStore %128 %137
+ OpBranch %129
+ %131 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %24 = OpFunction %2 None %3
+ %25 = OpLabel
+ %138 = OpVariable %39 Function
+ OpStore %138 %62
+ OpBranch %139
+ %139 = OpLabel
+ %233 = OpPhi %38 %62 %25 %147 %142
+ OpLoopMerge %141 %142 None
+ OpBranch %143
+ %143 = OpLabel
+ %145 = OpSGreaterThan %49 %233 %55
+ OpBranchConditional %145 %140 %141
+ %140 = OpLabel
+ OpBranch %142
+ %142 = OpLabel
+ %147 = OpISub %38 %233 %52
+ OpStore %138 %147
+ OpBranch %139
+ %141 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %26 = OpFunction %2 None %3
+ %27 = OpLabel
+ %148 = OpVariable %39 Function
+ OpStore %148 %73
+ OpBranch %149
+ %149 = OpLabel
+ %234 = OpPhi %38 %73 %27 %157 %152
+ OpLoopMerge %151 %152 None
+ OpBranch %153
+ %153 = OpLabel
+ %155 = OpSGreaterThan %49 %234 %48
+ OpBranchConditional %155 %150 %151
+ %150 = OpLabel
+ OpBranch %152
+ %152 = OpLabel
+ %157 = OpISub %38 %234 %52
+ OpStore %148 %157
+ OpBranch %149
+ %151 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %28 = OpFunction %2 None %3
+ %29 = OpLabel
+ %158 = OpVariable %39 Function
+ OpStore %158 %84
+ OpBranch %159
+ %159 = OpLabel
+ %235 = OpPhi %38 %84 %29 %167 %162
+ OpLoopMerge %161 %162 None
+ OpBranch %163
+ %163 = OpLabel
+ %165 = OpSGreaterThan %49 %235 %62
+ OpBranchConditional %165 %160 %161
+ %160 = OpLabel
+ OpBranch %162
+ %162 = OpLabel
+ %167 = OpISub %38 %235 %52
+ OpStore %158 %167
+ OpBranch %159
+ %161 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %30 = OpFunction %2 None %3
+ %31 = OpLabel
+ %168 = OpVariable %39 Function
+ OpStore %168 %48
+ OpBranch %169
+ %169 = OpLabel
+ %236 = OpPhi %38 %48 %31 %177 %172
+ OpLoopMerge %171 %172 None
+ OpBranch %173
+ %173 = OpLabel
+ %175 = OpSGreaterThanEqual %49 %236 %41
+ OpBranchConditional %175 %170 %171
+ %170 = OpLabel
+ OpBranch %172
+ %172 = OpLabel
+ %177 = OpISub %38 %236 %52
+ OpStore %168 %177
+ OpBranch %169
+ %171 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %32 = OpFunction %2 None %3
+ %33 = OpLabel
+ %178 = OpVariable %39 Function
+ OpStore %178 %62
+ OpBranch %179
+ %179 = OpLabel
+ %237 = OpPhi %38 %62 %33 %187 %182
+ OpLoopMerge %181 %182 None
+ OpBranch %183
+ %183 = OpLabel
+ %185 = OpSGreaterThanEqual %49 %237 %55
+ OpBranchConditional %185 %180 %181
+ %180 = OpLabel
+ OpBranch %182
+ %182 = OpLabel
+ %187 = OpISub %38 %237 %52
+ OpStore %178 %187
+ OpBranch %179
+ %181 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %34 = OpFunction %2 None %3
+ %35 = OpLabel
+ %188 = OpVariable %39 Function
+ OpStore %188 %73
+ OpBranch %189
+ %189 = OpLabel
+ %238 = OpPhi %38 %73 %35 %197 %192
+ OpLoopMerge %191 %192 None
+ OpBranch %193
+ %193 = OpLabel
+ %195 = OpSGreaterThanEqual %49 %238 %48
+ OpBranchConditional %195 %190 %191
+ %190 = OpLabel
+ OpBranch %192
+ %192 = OpLabel
+ %197 = OpISub %38 %238 %52
+ OpStore %188 %197
+ OpBranch %189
+ %191 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %36 = OpFunction %2 None %3
+ %37 = OpLabel
+ %198 = OpVariable %39 Function
+ OpStore %198 %84
+ OpBranch %199
+ %199 = OpLabel
+ %239 = OpPhi %38 %84 %37 %207 %202
+ OpLoopMerge %201 %202 None
+ OpBranch %203
+ %203 = OpLabel
+ %205 = OpSGreaterThanEqual %49 %239 %62
+ OpBranchConditional %205 %200 %201
+ %200 = OpLabel
+ OpBranch %202
+ %202 = OpLabel
+ %207 = OpISub %38 %239 %52
+ OpStore %198 %207
+ OpBranch %199
+ %201 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ {
+ // Function a
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -10);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -1);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(-10));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(-1));
+ }
+ {
+ // Function b
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -5);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 4);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(-5));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(4));
+ }
+ {
+ // Function c
+ const Function* f = spvtest::GetFunction(module, 10);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 0);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 9);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(0));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(9));
+ }
+ {
+ // Function d
+ const Function* f = spvtest::GetFunction(module, 12);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 5);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 14);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(5));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(14));
+ }
+ {
+ // Function e
+ const Function* f = spvtest::GetFunction(module, 14);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -10);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 0);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(-10));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(0));
+ }
+ {
+ // Function f
+ const Function* f = spvtest::GetFunction(module, 16);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -5);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 5);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(-5));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(5));
+ }
+ {
+ // Function g
+ const Function* f = spvtest::GetFunction(module, 18);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 0);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(0));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(10));
+ }
+ {
+ // Function h
+ const Function* f = spvtest::GetFunction(module, 20);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 5);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 15);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(5));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(1)),
+ analysis.GetScalarEvolution()->CreateConstant(15));
+ }
+ {
+ // Function i
+ const Function* f = spvtest::GetFunction(module, 22);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 0);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -9);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(0));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(-9));
+ }
+ {
+ // Function j
+ const Function* f = spvtest::GetFunction(module, 24);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 5);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -4);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(5));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(-4));
+ }
+ {
+ // Function k
+ const Function* f = spvtest::GetFunction(module, 26);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 1);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(10));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(1));
+ }
+ {
+ // Function l
+ const Function* f = spvtest::GetFunction(module, 28);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 15);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 6);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(15));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(6));
+ }
+ {
+ // Function m
+ const Function* f = spvtest::GetFunction(module, 30);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 0);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -10);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(0));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(-10));
+ }
+ {
+ // Function n
+ const Function* f = spvtest::GetFunction(module, 32);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 5);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ -5);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(5));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(-5));
+ }
+ {
+ // Function o
+ const Function* f = spvtest::GetFunction(module, 34);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 10);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 0);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(10));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(0));
+ }
+ {
+ // Function p
+ const Function* f = spvtest::GetFunction(module, 36);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_EQ(
+ analysis.GetLowerBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 15);
+ EXPECT_EQ(
+ analysis.GetUpperBound(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 5);
+
+ EXPECT_EQ(
+ analysis.GetTripCount(loop)->AsSEConstantNode()->FoldToSingleValue(),
+ 11);
+
+ EXPECT_EQ(analysis.GetFirstTripInductionNode(loop),
+ analysis.GetScalarEvolution()->CreateConstant(15));
+
+ EXPECT_EQ(analysis.GetFinalTripInductionNode(
+ loop, analysis.GetScalarEvolution()->CreateConstant(-1)),
+ analysis.GetScalarEvolution()->CreateConstant(5));
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+void main(){
+ for (int i = 0; i < 10; i++) {
+
+ }
+}
+*/
+TEST(DependencyAnalysisHelpers, bounds_checks) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %22 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %22 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %22 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ // We need a shader that includes a loop for this test so we can build a
+ // LoopDependenceAnalaysis
+ const Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ EXPECT_TRUE(analysis.IsWithinBounds(0, 0, 0));
+ EXPECT_TRUE(analysis.IsWithinBounds(0, -1, 0));
+ EXPECT_TRUE(analysis.IsWithinBounds(0, 0, 1));
+ EXPECT_TRUE(analysis.IsWithinBounds(0, -1, 1));
+ EXPECT_TRUE(analysis.IsWithinBounds(-2, -2, -2));
+ EXPECT_TRUE(analysis.IsWithinBounds(-2, -3, 0));
+ EXPECT_TRUE(analysis.IsWithinBounds(-2, 0, -3));
+ EXPECT_TRUE(analysis.IsWithinBounds(2, 2, 2));
+ EXPECT_TRUE(analysis.IsWithinBounds(2, 3, 0));
+
+ EXPECT_FALSE(analysis.IsWithinBounds(2, 3, 3));
+ EXPECT_FALSE(analysis.IsWithinBounds(0, 1, 5));
+ EXPECT_FALSE(analysis.IsWithinBounds(0, -1, -4));
+ EXPECT_FALSE(analysis.IsWithinBounds(-2, -4, -3));
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+layout(location = 0) in vec4 in_vec;
+// Loop iterates from constant to symbolic
+void a() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = 0; i < N; i++) { // Bounds are N - 0 - 1
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void b() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = 0; i <= N; i++) { // Bounds are N - 0
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void c() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = 9; i > N; i--) { // Bounds are 9 - N - 1
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void d() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = 9; i >= N; i--) { // Bounds are 9 - N
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void main(){
+ a();
+ b();
+ c();
+ d();
+}
+*/
+TEST(DependencyAnalysisHelpers, const_to_symbolic) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %20
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %10 "c("
+ OpName %12 "d("
+ OpName %16 "N"
+ OpName %20 "in_vec"
+ OpName %27 "i"
+ OpName %41 "arr"
+ OpName %59 "N"
+ OpName %63 "i"
+ OpName %72 "arr"
+ OpName %89 "N"
+ OpName %93 "i"
+ OpName %103 "arr"
+ OpName %120 "N"
+ OpName %124 "i"
+ OpName %133 "arr"
+ OpDecorate %20 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %17 = OpTypeFloat 32
+ %18 = OpTypeVector %17 4
+ %19 = OpTypePointer Input %18
+ %20 = OpVariable %19 Input
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 0
+ %23 = OpTypePointer Input %17
+ %28 = OpConstant %14 0
+ %36 = OpTypeBool
+ %38 = OpConstant %21 10
+ %39 = OpTypeArray %14 %38
+ %40 = OpTypePointer Function %39
+ %57 = OpConstant %14 1
+ %94 = OpConstant %14 9
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %150 = OpFunctionCall %2 %6
+ %151 = OpFunctionCall %2 %8
+ %152 = OpFunctionCall %2 %10
+ %153 = OpFunctionCall %2 %12
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %16 = OpVariable %15 Function
+ %27 = OpVariable %15 Function
+ %41 = OpVariable %40 Function
+ %24 = OpAccessChain %23 %20 %22
+ %25 = OpLoad %17 %24
+ %26 = OpConvertFToS %14 %25
+ OpStore %16 %26
+ OpStore %27 %28
+ OpBranch %29
+ %29 = OpLabel
+ %154 = OpPhi %14 %28 %7 %58 %32
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+ %37 = OpSLessThan %36 %154 %26
+ OpBranchConditional %37 %30 %31
+ %30 = OpLabel
+ %45 = OpIAdd %14 %154 %26
+ %46 = OpAccessChain %15 %41 %45
+ %47 = OpLoad %14 %46
+ %48 = OpAccessChain %15 %41 %154
+ OpStore %48 %47
+ %51 = OpIAdd %14 %154 %26
+ %53 = OpAccessChain %15 %41 %154
+ %54 = OpLoad %14 %53
+ %55 = OpAccessChain %15 %41 %51
+ OpStore %55 %54
+ OpBranch %32
+ %32 = OpLabel
+ %58 = OpIAdd %14 %154 %57
+ OpStore %27 %58
+ OpBranch %29
+ %31 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %59 = OpVariable %15 Function
+ %63 = OpVariable %15 Function
+ %72 = OpVariable %40 Function
+ %60 = OpAccessChain %23 %20 %22
+ %61 = OpLoad %17 %60
+ %62 = OpConvertFToS %14 %61
+ OpStore %59 %62
+ OpStore %63 %28
+ OpBranch %64
+ %64 = OpLabel
+ %155 = OpPhi %14 %28 %9 %88 %67
+ OpLoopMerge %66 %67 None
+ OpBranch %68
+ %68 = OpLabel
+ %71 = OpSLessThanEqual %36 %155 %62
+ OpBranchConditional %71 %65 %66
+ %65 = OpLabel
+ %76 = OpIAdd %14 %155 %62
+ %77 = OpAccessChain %15 %72 %76
+ %78 = OpLoad %14 %77
+ %79 = OpAccessChain %15 %72 %155
+ OpStore %79 %78
+ %82 = OpIAdd %14 %155 %62
+ %84 = OpAccessChain %15 %72 %155
+ %85 = OpLoad %14 %84
+ %86 = OpAccessChain %15 %72 %82
+ OpStore %86 %85
+ OpBranch %67
+ %67 = OpLabel
+ %88 = OpIAdd %14 %155 %57
+ OpStore %63 %88
+ OpBranch %64
+ %66 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %89 = OpVariable %15 Function
+ %93 = OpVariable %15 Function
+ %103 = OpVariable %40 Function
+ %90 = OpAccessChain %23 %20 %22
+ %91 = OpLoad %17 %90
+ %92 = OpConvertFToS %14 %91
+ OpStore %89 %92
+ OpStore %93 %94
+ OpBranch %95
+ %95 = OpLabel
+ %156 = OpPhi %14 %94 %11 %119 %98
+ OpLoopMerge %97 %98 None
+ OpBranch %99
+ %99 = OpLabel
+ %102 = OpSGreaterThan %36 %156 %92
+ OpBranchConditional %102 %96 %97
+ %96 = OpLabel
+ %107 = OpIAdd %14 %156 %92
+ %108 = OpAccessChain %15 %103 %107
+ %109 = OpLoad %14 %108
+ %110 = OpAccessChain %15 %103 %156
+ OpStore %110 %109
+ %113 = OpIAdd %14 %156 %92
+ %115 = OpAccessChain %15 %103 %156
+ %116 = OpLoad %14 %115
+ %117 = OpAccessChain %15 %103 %113
+ OpStore %117 %116
+ OpBranch %98
+ %98 = OpLabel
+ %119 = OpISub %14 %156 %57
+ OpStore %93 %119
+ OpBranch %95
+ %97 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %120 = OpVariable %15 Function
+ %124 = OpVariable %15 Function
+ %133 = OpVariable %40 Function
+ %121 = OpAccessChain %23 %20 %22
+ %122 = OpLoad %17 %121
+ %123 = OpConvertFToS %14 %122
+ OpStore %120 %123
+ OpStore %124 %94
+ OpBranch %125
+ %125 = OpLabel
+ %157 = OpPhi %14 %94 %13 %149 %128
+ OpLoopMerge %127 %128 None
+ OpBranch %129
+ %129 = OpLabel
+ %132 = OpSGreaterThanEqual %36 %157 %123
+ OpBranchConditional %132 %126 %127
+ %126 = OpLabel
+ %137 = OpIAdd %14 %157 %123
+ %138 = OpAccessChain %15 %133 %137
+ %139 = OpLoad %14 %138
+ %140 = OpAccessChain %15 %133 %157
+ OpStore %140 %139
+ %143 = OpIAdd %14 %157 %123
+ %145 = OpAccessChain %15 %133 %157
+ %146 = OpLoad %14 %145
+ %147 = OpAccessChain %15 %133 %143
+ OpStore %147 %146
+ OpBranch %128
+ %128 = OpLabel
+ %149 = OpISub %14 %157 %57
+ OpStore %124 %149
+ OpBranch %125
+ %127 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ {
+ // Function a
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 30)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 47 -> 48
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(47)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent and supported.
+ EXPECT_TRUE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 54 -> 55
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(54)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent but not supported.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function b
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 65)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 78 -> 79
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(78)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 85 -> 86
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(85)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function c
+ const Function* f = spvtest::GetFunction(module, 10);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 96)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 109 -> 110
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(109)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent but not supported.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 116 -> 117
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(116)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent but not supported.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function d
+ const Function* f = spvtest::GetFunction(module, 12);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 126)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 139 -> 140
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(139)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 146 -> 147
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(146)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+layout(location = 0) in vec4 in_vec;
+// Loop iterates from symbolic to constant
+void a() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = N; i < 9; i++) { // Bounds are 9 - N - 1
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void b() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = N; i <= 9; i++) { // Bounds are 9 - N
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void c() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = N; i > 0; i--) { // Bounds are N - 0 - 1
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void d() {
+ int N = int(in_vec.x);
+ int arr[10];
+ for (int i = N; i >= 0; i--) { // Bounds are N - 0
+ arr[i] = arr[i+N]; // |distance| = N
+ arr[i+N] = arr[i]; // |distance| = N
+ }
+}
+void main(){
+ a();
+ b();
+ c();
+ d();
+}
+*/
+TEST(DependencyAnalysisHelpers, symbolic_to_const) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %20
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %10 "c("
+ OpName %12 "d("
+ OpName %16 "N"
+ OpName %20 "in_vec"
+ OpName %27 "i"
+ OpName %41 "arr"
+ OpName %59 "N"
+ OpName %63 "i"
+ OpName %72 "arr"
+ OpName %89 "N"
+ OpName %93 "i"
+ OpName %103 "arr"
+ OpName %120 "N"
+ OpName %124 "i"
+ OpName %133 "arr"
+ OpDecorate %20 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %17 = OpTypeFloat 32
+ %18 = OpTypeVector %17 4
+ %19 = OpTypePointer Input %18
+ %20 = OpVariable %19 Input
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 0
+ %23 = OpTypePointer Input %17
+ %35 = OpConstant %14 9
+ %36 = OpTypeBool
+ %38 = OpConstant %21 10
+ %39 = OpTypeArray %14 %38
+ %40 = OpTypePointer Function %39
+ %57 = OpConstant %14 1
+ %101 = OpConstant %14 0
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %150 = OpFunctionCall %2 %6
+ %151 = OpFunctionCall %2 %8
+ %152 = OpFunctionCall %2 %10
+ %153 = OpFunctionCall %2 %12
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %16 = OpVariable %15 Function
+ %27 = OpVariable %15 Function
+ %41 = OpVariable %40 Function
+ %24 = OpAccessChain %23 %20 %22
+ %25 = OpLoad %17 %24
+ %26 = OpConvertFToS %14 %25
+ OpStore %16 %26
+ OpStore %27 %26
+ OpBranch %29
+ %29 = OpLabel
+ %154 = OpPhi %14 %26 %7 %58 %32
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+ %37 = OpSLessThan %36 %154 %35
+ OpBranchConditional %37 %30 %31
+ %30 = OpLabel
+ %45 = OpIAdd %14 %154 %26
+ %46 = OpAccessChain %15 %41 %45
+ %47 = OpLoad %14 %46
+ %48 = OpAccessChain %15 %41 %154
+ OpStore %48 %47
+ %51 = OpIAdd %14 %154 %26
+ %53 = OpAccessChain %15 %41 %154
+ %54 = OpLoad %14 %53
+ %55 = OpAccessChain %15 %41 %51
+ OpStore %55 %54
+ OpBranch %32
+ %32 = OpLabel
+ %58 = OpIAdd %14 %154 %57
+ OpStore %27 %58
+ OpBranch %29
+ %31 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %59 = OpVariable %15 Function
+ %63 = OpVariable %15 Function
+ %72 = OpVariable %40 Function
+ %60 = OpAccessChain %23 %20 %22
+ %61 = OpLoad %17 %60
+ %62 = OpConvertFToS %14 %61
+ OpStore %59 %62
+ OpStore %63 %62
+ OpBranch %65
+ %65 = OpLabel
+ %155 = OpPhi %14 %62 %9 %88 %68
+ OpLoopMerge %67 %68 None
+ OpBranch %69
+ %69 = OpLabel
+ %71 = OpSLessThanEqual %36 %155 %35
+ OpBranchConditional %71 %66 %67
+ %66 = OpLabel
+ %76 = OpIAdd %14 %155 %62
+ %77 = OpAccessChain %15 %72 %76
+ %78 = OpLoad %14 %77
+ %79 = OpAccessChain %15 %72 %155
+ OpStore %79 %78
+ %82 = OpIAdd %14 %155 %62
+ %84 = OpAccessChain %15 %72 %155
+ %85 = OpLoad %14 %84
+ %86 = OpAccessChain %15 %72 %82
+ OpStore %86 %85
+ OpBranch %68
+ %68 = OpLabel
+ %88 = OpIAdd %14 %155 %57
+ OpStore %63 %88
+ OpBranch %65
+ %67 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %89 = OpVariable %15 Function
+ %93 = OpVariable %15 Function
+ %103 = OpVariable %40 Function
+ %90 = OpAccessChain %23 %20 %22
+ %91 = OpLoad %17 %90
+ %92 = OpConvertFToS %14 %91
+ OpStore %89 %92
+ OpStore %93 %92
+ OpBranch %95
+ %95 = OpLabel
+ %156 = OpPhi %14 %92 %11 %119 %98
+ OpLoopMerge %97 %98 None
+ OpBranch %99
+ %99 = OpLabel
+ %102 = OpSGreaterThan %36 %156 %101
+ OpBranchConditional %102 %96 %97
+ %96 = OpLabel
+ %107 = OpIAdd %14 %156 %92
+ %108 = OpAccessChain %15 %103 %107
+ %109 = OpLoad %14 %108
+ %110 = OpAccessChain %15 %103 %156
+ OpStore %110 %109
+ %113 = OpIAdd %14 %156 %92
+ %115 = OpAccessChain %15 %103 %156
+ %116 = OpLoad %14 %115
+ %117 = OpAccessChain %15 %103 %113
+ OpStore %117 %116
+ OpBranch %98
+ %98 = OpLabel
+ %119 = OpISub %14 %156 %57
+ OpStore %93 %119
+ OpBranch %95
+ %97 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %120 = OpVariable %15 Function
+ %124 = OpVariable %15 Function
+ %133 = OpVariable %40 Function
+ %121 = OpAccessChain %23 %20 %22
+ %122 = OpLoad %17 %121
+ %123 = OpConvertFToS %14 %122
+ OpStore %120 %123
+ OpStore %124 %123
+ OpBranch %126
+ %126 = OpLabel
+ %157 = OpPhi %14 %123 %13 %149 %129
+ OpLoopMerge %128 %129 None
+ OpBranch %130
+ %130 = OpLabel
+ %132 = OpSGreaterThanEqual %36 %157 %101
+ OpBranchConditional %132 %127 %128
+ %127 = OpLabel
+ %137 = OpIAdd %14 %157 %123
+ %138 = OpAccessChain %15 %133 %137
+ %139 = OpLoad %14 %138
+ %140 = OpAccessChain %15 %133 %157
+ OpStore %140 %139
+ %143 = OpIAdd %14 %157 %123
+ %145 = OpAccessChain %15 %133 %157
+ %146 = OpLoad %14 %145
+ %147 = OpAccessChain %15 %133 %143
+ OpStore %147 %146
+ OpBranch %129
+ %129 = OpLabel
+ %149 = OpISub %14 %157 %57
+ OpStore %124 %149
+ OpBranch %126
+ %128 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ {
+ // Function a
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 30)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 47 -> 48
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(47)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent but not supported.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 54 -> 55
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(54)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent but not supported.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function b
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 66)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 78 -> 79
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(78)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 85 -> 86
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(85)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function c
+ const Function* f = spvtest::GetFunction(module, 10);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 96)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 109 -> 110
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(109)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent and supported.
+ EXPECT_TRUE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 116 -> 117
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(116)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Independent but not supported.
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function d
+ const Function* f = spvtest::GetFunction(module, 12);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 127)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 139 -> 140
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(139)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 146 -> 147
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(146)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ // Dependent
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+}
+
+/*
+ Generated from the following GLSL fragment shader
+ with --eliminate-local-multi-store
+#version 440 core
+layout(location = 0) in vec4 in_vec;
+// Loop iterates from symbolic to symbolic
+void a() {
+ int M = int(in_vec.x);
+ int N = int(in_vec.y);
+ int arr[10];
+ for (int i = M; i < N; i++) { // Bounds are N - M - 1
+ arr[i+M+N] = arr[i+M+2*N]; // |distance| = N
+ arr[i+M+2*N] = arr[i+M+N]; // |distance| = N
+ }
+}
+void b() {
+ int M = int(in_vec.x);
+ int N = int(in_vec.y);
+ int arr[10];
+ for (int i = M; i <= N; i++) { // Bounds are N - M
+ arr[i+M+N] = arr[i+M+2*N]; // |distance| = N
+ arr[i+M+2*N] = arr[i+M+N]; // |distance| = N
+ }
+}
+void c() {
+ int M = int(in_vec.x);
+ int N = int(in_vec.y);
+ int arr[10];
+ for (int i = M; i > N; i--) { // Bounds are M - N - 1
+ arr[i+M+N] = arr[i+M+2*N]; // |distance| = N
+ arr[i+M+2*N] = arr[i+M+N]; // |distance| = N
+ }
+}
+void d() {
+ int M = int(in_vec.x);
+ int N = int(in_vec.y);
+ int arr[10];
+ for (int i = M; i >= N; i--) { // Bounds are M - N
+ arr[i+M+N] = arr[i+M+2*N]; // |distance| = N
+ arr[i+M+2*N] = arr[i+M+N]; // |distance| = N
+ }
+}
+void main(){
+ a();
+ b();
+ c();
+ d();
+}
+*/
+TEST(DependencyAnalysisHelpers, symbolic_to_symbolic) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %20
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %6 "a("
+ OpName %8 "b("
+ OpName %10 "c("
+ OpName %12 "d("
+ OpName %16 "M"
+ OpName %20 "in_vec"
+ OpName %27 "N"
+ OpName %32 "i"
+ OpName %46 "arr"
+ OpName %79 "M"
+ OpName %83 "N"
+ OpName %87 "i"
+ OpName %97 "arr"
+ OpName %128 "M"
+ OpName %132 "N"
+ OpName %136 "i"
+ OpName %146 "arr"
+ OpName %177 "M"
+ OpName %181 "N"
+ OpName %185 "i"
+ OpName %195 "arr"
+ OpDecorate %20 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %17 = OpTypeFloat 32
+ %18 = OpTypeVector %17 4
+ %19 = OpTypePointer Input %18
+ %20 = OpVariable %19 Input
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 0
+ %23 = OpTypePointer Input %17
+ %28 = OpConstant %21 1
+ %41 = OpTypeBool
+ %43 = OpConstant %21 10
+ %44 = OpTypeArray %14 %43
+ %45 = OpTypePointer Function %44
+ %55 = OpConstant %14 2
+ %77 = OpConstant %14 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %226 = OpFunctionCall %2 %6
+ %227 = OpFunctionCall %2 %8
+ %228 = OpFunctionCall %2 %10
+ %229 = OpFunctionCall %2 %12
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %16 = OpVariable %15 Function
+ %27 = OpVariable %15 Function
+ %32 = OpVariable %15 Function
+ %46 = OpVariable %45 Function
+ %24 = OpAccessChain %23 %20 %22
+ %25 = OpLoad %17 %24
+ %26 = OpConvertFToS %14 %25
+ OpStore %16 %26
+ %29 = OpAccessChain %23 %20 %28
+ %30 = OpLoad %17 %29
+ %31 = OpConvertFToS %14 %30
+ OpStore %27 %31
+ OpStore %32 %26
+ OpBranch %34
+ %34 = OpLabel
+ %230 = OpPhi %14 %26 %7 %78 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %42 = OpSLessThan %41 %230 %31
+ OpBranchConditional %42 %35 %36
+ %35 = OpLabel
+ %49 = OpIAdd %14 %230 %26
+ %51 = OpIAdd %14 %49 %31
+ %54 = OpIAdd %14 %230 %26
+ %57 = OpIMul %14 %55 %31
+ %58 = OpIAdd %14 %54 %57
+ %59 = OpAccessChain %15 %46 %58
+ %60 = OpLoad %14 %59
+ %61 = OpAccessChain %15 %46 %51
+ OpStore %61 %60
+ %64 = OpIAdd %14 %230 %26
+ %66 = OpIMul %14 %55 %31
+ %67 = OpIAdd %14 %64 %66
+ %70 = OpIAdd %14 %230 %26
+ %72 = OpIAdd %14 %70 %31
+ %73 = OpAccessChain %15 %46 %72
+ %74 = OpLoad %14 %73
+ %75 = OpAccessChain %15 %46 %67
+ OpStore %75 %74
+ OpBranch %37
+ %37 = OpLabel
+ %78 = OpIAdd %14 %230 %77
+ OpStore %32 %78
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %2 None %3
+ %9 = OpLabel
+ %79 = OpVariable %15 Function
+ %83 = OpVariable %15 Function
+ %87 = OpVariable %15 Function
+ %97 = OpVariable %45 Function
+ %80 = OpAccessChain %23 %20 %22
+ %81 = OpLoad %17 %80
+ %82 = OpConvertFToS %14 %81
+ OpStore %79 %82
+ %84 = OpAccessChain %23 %20 %28
+ %85 = OpLoad %17 %84
+ %86 = OpConvertFToS %14 %85
+ OpStore %83 %86
+ OpStore %87 %82
+ OpBranch %89
+ %89 = OpLabel
+ %231 = OpPhi %14 %82 %9 %127 %92
+ OpLoopMerge %91 %92 None
+ OpBranch %93
+ %93 = OpLabel
+ %96 = OpSLessThanEqual %41 %231 %86
+ OpBranchConditional %96 %90 %91
+ %90 = OpLabel
+ %100 = OpIAdd %14 %231 %82
+ %102 = OpIAdd %14 %100 %86
+ %105 = OpIAdd %14 %231 %82
+ %107 = OpIMul %14 %55 %86
+ %108 = OpIAdd %14 %105 %107
+ %109 = OpAccessChain %15 %97 %108
+ %110 = OpLoad %14 %109
+ %111 = OpAccessChain %15 %97 %102
+ OpStore %111 %110
+ %114 = OpIAdd %14 %231 %82
+ %116 = OpIMul %14 %55 %86
+ %117 = OpIAdd %14 %114 %116
+ %120 = OpIAdd %14 %231 %82
+ %122 = OpIAdd %14 %120 %86
+ %123 = OpAccessChain %15 %97 %122
+ %124 = OpLoad %14 %123
+ %125 = OpAccessChain %15 %97 %117
+ OpStore %125 %124
+ OpBranch %92
+ %92 = OpLabel
+ %127 = OpIAdd %14 %231 %77
+ OpStore %87 %127
+ OpBranch %89
+ %91 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %3
+ %11 = OpLabel
+ %128 = OpVariable %15 Function
+ %132 = OpVariable %15 Function
+ %136 = OpVariable %15 Function
+ %146 = OpVariable %45 Function
+ %129 = OpAccessChain %23 %20 %22
+ %130 = OpLoad %17 %129
+ %131 = OpConvertFToS %14 %130
+ OpStore %128 %131
+ %133 = OpAccessChain %23 %20 %28
+ %134 = OpLoad %17 %133
+ %135 = OpConvertFToS %14 %134
+ OpStore %132 %135
+ OpStore %136 %131
+ OpBranch %138
+ %138 = OpLabel
+ %232 = OpPhi %14 %131 %11 %176 %141
+ OpLoopMerge %140 %141 None
+ OpBranch %142
+ %142 = OpLabel
+ %145 = OpSGreaterThan %41 %232 %135
+ OpBranchConditional %145 %139 %140
+ %139 = OpLabel
+ %149 = OpIAdd %14 %232 %131
+ %151 = OpIAdd %14 %149 %135
+ %154 = OpIAdd %14 %232 %131
+ %156 = OpIMul %14 %55 %135
+ %157 = OpIAdd %14 %154 %156
+ %158 = OpAccessChain %15 %146 %157
+ %159 = OpLoad %14 %158
+ %160 = OpAccessChain %15 %146 %151
+ OpStore %160 %159
+ %163 = OpIAdd %14 %232 %131
+ %165 = OpIMul %14 %55 %135
+ %166 = OpIAdd %14 %163 %165
+ %169 = OpIAdd %14 %232 %131
+ %171 = OpIAdd %14 %169 %135
+ %172 = OpAccessChain %15 %146 %171
+ %173 = OpLoad %14 %172
+ %174 = OpAccessChain %15 %146 %166
+ OpStore %174 %173
+ OpBranch %141
+ %141 = OpLabel
+ %176 = OpISub %14 %232 %77
+ OpStore %136 %176
+ OpBranch %138
+ %140 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ %177 = OpVariable %15 Function
+ %181 = OpVariable %15 Function
+ %185 = OpVariable %15 Function
+ %195 = OpVariable %45 Function
+ %178 = OpAccessChain %23 %20 %22
+ %179 = OpLoad %17 %178
+ %180 = OpConvertFToS %14 %179
+ OpStore %177 %180
+ %182 = OpAccessChain %23 %20 %28
+ %183 = OpLoad %17 %182
+ %184 = OpConvertFToS %14 %183
+ OpStore %181 %184
+ OpStore %185 %180
+ OpBranch %187
+ %187 = OpLabel
+ %233 = OpPhi %14 %180 %13 %225 %190
+ OpLoopMerge %189 %190 None
+ OpBranch %191
+ %191 = OpLabel
+ %194 = OpSGreaterThanEqual %41 %233 %184
+ OpBranchConditional %194 %188 %189
+ %188 = OpLabel
+ %198 = OpIAdd %14 %233 %180
+ %200 = OpIAdd %14 %198 %184
+ %203 = OpIAdd %14 %233 %180
+ %205 = OpIMul %14 %55 %184
+ %206 = OpIAdd %14 %203 %205
+ %207 = OpAccessChain %15 %195 %206
+ %208 = OpLoad %14 %207
+ %209 = OpAccessChain %15 %195 %200
+ OpStore %209 %208
+ %212 = OpIAdd %14 %233 %180
+ %214 = OpIMul %14 %55 %184
+ %215 = OpIAdd %14 %212 %214
+ %218 = OpIAdd %14 %233 %180
+ %220 = OpIAdd %14 %218 %184
+ %221 = OpAccessChain %15 %195 %220
+ %222 = OpLoad %14 %221
+ %223 = OpAccessChain %15 %195 %215
+ OpStore %223 %222
+ OpBranch %190
+ %190 = OpLabel
+ %225 = OpISub %14 %233 %77
+ OpStore %185 %225
+ OpBranch %187
+ %189 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ {
+ // Function a
+ const Function* f = spvtest::GetFunction(module, 6);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 35)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 60 -> 61
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(60)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 74 -> 75
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(74)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function b
+ const Function* f = spvtest::GetFunction(module, 8);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 90)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 110 -> 111
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(110)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 124 -> 125
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(124)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function c
+ const Function* f = spvtest::GetFunction(module, 10);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 139)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 159 -> 160
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(159)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 173 -> 174
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(173)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+ {
+ // Function d
+ const Function* f = spvtest::GetFunction(module, 12);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ Loop* loop = &ld.GetLoopByIndex(0);
+ std::vector<const Loop*> loops{loop};
+ LoopDependenceAnalysis analysis{context.get(), loops};
+
+ const Instruction* stores[2];
+ int stores_found = 0;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 188)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[stores_found] = &inst;
+ ++stores_found;
+ }
+ }
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_TRUE(stores[i]);
+ }
+
+ // 208 -> 209
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(208)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[0]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+
+ // 222 -> 223
+ {
+ // Analyse and simplify the instruction behind the access chain of this
+ // load.
+ Instruction* load_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(context->get_def_use_mgr()
+ ->GetDef(222)
+ ->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* load = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(load_var));
+
+ // Analyse and simplify the instruction behind the access chain of this
+ // store.
+ Instruction* store_var = context->get_def_use_mgr()->GetDef(
+ context->get_def_use_mgr()
+ ->GetDef(stores[1]->GetSingleWordInOperand(0))
+ ->GetSingleWordInOperand(1));
+ SENode* store = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->AnalyzeInstruction(store_var));
+
+ SENode* delta = analysis.GetScalarEvolution()->SimplifyExpression(
+ analysis.GetScalarEvolution()->CreateSubtraction(load, store));
+
+ EXPECT_FALSE(analysis.IsProvablyOutsideOfLoopBounds(
+ loop, delta, store->AsSERecurrentNode()->GetCoefficient()));
+ }
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/fusion_compatibility.cpp b/test/opt/loop_optimizations/fusion_compatibility.cpp
new file mode 100644
index 0000000..cda8576
--- /dev/null
+++ b/test/opt/loop_optimizations/fusion_compatibility.cpp
@@ -0,0 +1,1785 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <algorithm>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/loop_fusion.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FusionCompatibilityTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int i = 0; // Can't fuse, i=0 in first & i=10 in second
+ for (; i < 10; i++) {}
+ for (; i < 10; i++) {}
+}
+*/
+TEST_F(FusionCompatibilityTest, SameInductionVariableDifferentBounds) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %31 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %31 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %31 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpBranch %22
+ %22 = OpLabel
+ %32 = OpPhi %6 %31 %12 %30 %25
+ OpLoopMerge %24 %25 None
+ OpBranch %26
+ %26 = OpLabel
+ %28 = OpSLessThan %17 %32 %16
+ OpBranchConditional %28 %23 %24
+ %23 = OpLabel
+ OpBranch %25
+ %25 = OpLabel
+ %30 = OpIAdd %6 %32 %20
+ OpStore %8 %30
+ OpBranch %22
+ %24 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 1
+#version 440 core
+void main() {
+ for (int i = 0; i < 10; i++) {}
+ for (int i = 0; i < 10; i++) {}
+}
+*/
+TEST_F(FusionCompatibilityTest, Compatible) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %32 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %32 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %32 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %33 = OpPhi %6 %9 %12 %31 %26
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %17 %33 %16
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %31 = OpIAdd %6 %33 %20
+ OpStore %22 %31
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 2
+#version 440 core
+void main() {
+ for (int i = 0; i < 10; i++) {}
+ for (int j = 0; j < 10; j++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, DifferentName) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %32 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %32 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %32 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %33 = OpPhi %6 %9 %12 %31 %26
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %17 %33 %16
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %31 = OpIAdd %6 %33 %20
+ OpStore %22 %31
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ // Can't fuse, different step
+ for (int i = 0; i < 10; i++) {}
+ for (int j = 0; j < 10; j=j+2) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, SameBoundsDifferentStep) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %31 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %33 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %33 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %33 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %34 = OpPhi %6 %9 %12 %32 %26
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %17 %34 %16
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %32 = OpIAdd %6 %34 %31
+ OpStore %22 %32
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 4
+#version 440 core
+void main() {
+ // Can't fuse, different upper bound
+ for (int i = 0; i < 10; i++) {}
+ for (int j = 0; j < 20; j++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, DifferentUpperBound) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %29 = OpConstant %6 20
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %33 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %33 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %33 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %34 = OpPhi %6 %9 %12 %32 %26
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %30 = OpSLessThan %17 %34 %29
+ OpBranchConditional %30 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %32 = OpIAdd %6 %34 %20
+ OpStore %22 %32
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 5
+#version 440 core
+void main() {
+ // Can't fuse, different lower bound
+ for (int i = 5; i < 10; i++) {}
+ for (int j = 0; j < 10; j++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, DifferentLowerBound) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 5
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %23 = OpConstant %6 0
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %33 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %33 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %33 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %23
+ OpBranch %24
+ %24 = OpLabel
+ %34 = OpPhi %6 %23 %12 %32 %27
+ OpLoopMerge %26 %27 None
+ OpBranch %28
+ %28 = OpLabel
+ %30 = OpSLessThan %17 %34 %16
+ OpBranchConditional %30 %25 %26
+ %25 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %32 = OpIAdd %6 %34 %20
+ OpStore %22 %32
+ OpBranch %24
+ %26 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 6
+#version 440 core
+void main() {
+ // Can't fuse, break in first loop
+ for (int i = 0; i < 10; i++) {
+ if (i == 5) {
+ break;
+ }
+ }
+ for (int j = 0; j < 10; j++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, Break) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %28 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 5
+ %26 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %28 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %38 = OpPhi %6 %9 %5 %27 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %38 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %21 = OpIEqual %17 %38 %20
+ OpSelectionMerge %23 None
+ OpBranchConditional %21 %22 %23
+ %22 = OpLabel
+ OpBranch %12
+ %23 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %27 = OpIAdd %6 %38 %26
+ OpStore %8 %27
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %28 %9
+ OpBranch %29
+ %29 = OpLabel
+ %39 = OpPhi %6 %9 %12 %37 %32
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+ %35 = OpSLessThan %17 %39 %16
+ OpBranchConditional %35 %30 %31
+ %30 = OpLabel
+ OpBranch %32
+ %32 = OpLabel
+ %37 = OpIAdd %6 %39 %26
+ OpStore %28 %37
+ OpBranch %29
+ %31 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+layout(location = 0) in vec4 c;
+void main() {
+ int N = int(c.x);
+ for (int i = 0; i < N; i++) {}
+ for (int j = 0; j < N; j++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, UnknownButSameUpperBound) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %12
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "N"
+ OpName %12 "c"
+ OpName %19 "i"
+ OpName %33 "j"
+ OpDecorate %12 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpTypeFloat 32
+ %10 = OpTypeVector %9 4
+ %11 = OpTypePointer Input %10
+ %12 = OpVariable %11 Input
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 0
+ %15 = OpTypePointer Input %9
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %33 = OpVariable %7 Function
+ %16 = OpAccessChain %15 %12 %14
+ %17 = OpLoad %9 %16
+ %18 = OpConvertFToS %6 %17
+ OpStore %8 %18
+ OpStore %19 %20
+ OpBranch %21
+ %21 = OpLabel
+ %44 = OpPhi %6 %20 %5 %32 %24
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %29 = OpSLessThan %28 %44 %18
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ %32 = OpIAdd %6 %44 %31
+ OpStore %19 %32
+ OpBranch %21
+ %23 = OpLabel
+ OpStore %33 %20
+ OpBranch %34
+ %34 = OpLabel
+ %46 = OpPhi %6 %20 %23 %43 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %41 = OpSLessThan %28 %46 %18
+ OpBranchConditional %41 %35 %36
+ %35 = OpLabel
+ OpBranch %37
+ %37 = OpLabel
+ %43 = OpIAdd %6 %46 %31
+ OpStore %33 %43
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+layout(location = 0) in vec4 c;
+void main() {
+ int N = int(c.x);
+ for (int i = 0; N > j; i++) {}
+ for (int j = 0; N > j; j++) {}
+}
+*/
+TEST_F(FusionCompatibilityTest, UnknownButSameUpperBoundReverseCondition) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %12
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "N"
+ OpName %12 "c"
+ OpName %19 "i"
+ OpName %33 "j"
+ OpDecorate %12 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpTypeFloat 32
+ %10 = OpTypeVector %9 4
+ %11 = OpTypePointer Input %10
+ %12 = OpVariable %11 Input
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 0
+ %15 = OpTypePointer Input %9
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %33 = OpVariable %7 Function
+ %16 = OpAccessChain %15 %12 %14
+ %17 = OpLoad %9 %16
+ %18 = OpConvertFToS %6 %17
+ OpStore %8 %18
+ OpStore %19 %20
+ OpBranch %21
+ %21 = OpLabel
+ %45 = OpPhi %6 %20 %5 %32 %24
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %29 = OpSGreaterThan %28 %18 %45
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ %32 = OpIAdd %6 %45 %31
+ OpStore %19 %32
+ OpBranch %21
+ %23 = OpLabel
+ OpStore %33 %20
+ OpBranch %34
+ %34 = OpLabel
+ %47 = OpPhi %6 %20 %23 %43 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %41 = OpSGreaterThan %28 %18 %47
+ OpBranchConditional %41 %35 %36
+ %35 = OpLabel
+ OpBranch %37
+ %37 = OpLabel
+ %43 = OpIAdd %6 %47 %31
+ OpStore %33 %43
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+layout(location = 0) in vec4 c;
+void main() {
+ // Can't fuse different bound
+ int N = int(c.x);
+ for (int i = 0; i < N; i++) {}
+ for (int j = 0; j < N+1; j++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, UnknownUpperBoundAddition) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %12
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "N"
+ OpName %12 "c"
+ OpName %19 "i"
+ OpName %33 "j"
+ OpDecorate %12 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpTypeFloat 32
+ %10 = OpTypeVector %9 4
+ %11 = OpTypePointer Input %10
+ %12 = OpVariable %11 Input
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 0
+ %15 = OpTypePointer Input %9
+ %20 = OpConstant %6 0
+ %28 = OpTypeBool
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %33 = OpVariable %7 Function
+ %16 = OpAccessChain %15 %12 %14
+ %17 = OpLoad %9 %16
+ %18 = OpConvertFToS %6 %17
+ OpStore %8 %18
+ OpStore %19 %20
+ OpBranch %21
+ %21 = OpLabel
+ %45 = OpPhi %6 %20 %5 %32 %24
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %29 = OpSLessThan %28 %45 %18
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ %32 = OpIAdd %6 %45 %31
+ OpStore %19 %32
+ OpBranch %21
+ %23 = OpLabel
+ OpStore %33 %20
+ OpBranch %34
+ %34 = OpLabel
+ %47 = OpPhi %6 %20 %23 %44 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %41 = OpIAdd %6 %18 %31
+ %42 = OpSLessThan %28 %47 %41
+ OpBranchConditional %42 %35 %36
+ %35 = OpLabel
+ OpBranch %37
+ %37 = OpLabel
+ %44 = OpIAdd %6 %47 %31
+ OpStore %33 %44
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 10
+#version 440 core
+void main() {
+ for (int i = 0; i < 10; i++) {}
+ for (int j = 0; j < 10; j++) {}
+ for (int k = 0; k < 10; k++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, SeveralAdjacentLoops) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "j"
+ OpName %32 "k"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ %32 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %42 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %42 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %42 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %43 = OpPhi %6 %9 %12 %31 %26
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %17 %43 %16
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %31 = OpIAdd %6 %43 %20
+ OpStore %22 %31
+ OpBranch %23
+ %25 = OpLabel
+ OpStore %32 %9
+ OpBranch %33
+ %33 = OpLabel
+ %44 = OpPhi %6 %9 %25 %41 %36
+ OpLoopMerge %35 %36 None
+ OpBranch %37
+ %37 = OpLabel
+ %39 = OpSLessThan %17 %44 %16
+ OpBranchConditional %39 %34 %35
+ %34 = OpLabel
+ OpBranch %36
+ %36 = OpLabel
+ %41 = OpIAdd %6 %44 %20
+ OpStore %32 %41
+ OpBranch %33
+ %35 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+
+ EXPECT_FALSE(LoopFusion(context.get(), loop_0, loop_0).AreCompatible());
+ EXPECT_FALSE(LoopFusion(context.get(), loop_0, loop_2).AreCompatible());
+ EXPECT_FALSE(LoopFusion(context.get(), loop_1, loop_0).AreCompatible());
+ EXPECT_TRUE(LoopFusion(context.get(), loop_0, loop_1).AreCompatible());
+ EXPECT_TRUE(LoopFusion(context.get(), loop_1, loop_2).AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ // Can't fuse, not adjacent
+ int x = 0;
+ for (int i = 0; i < 10; i++) {
+ if (i > 10) {
+ x++;
+ }
+ }
+ x++;
+ for (int j = 0; j < 10; j++) {}
+ for (int k = 0; k < 10; k++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, NonAdjacentLoops) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "x"
+ OpName %10 "i"
+ OpName %31 "j"
+ OpName %41 "k"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 10
+ %18 = OpTypeBool
+ %25 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %31 = OpVariable %7 Function
+ %41 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpBranch %11
+ %11 = OpLabel
+ %52 = OpPhi %6 %9 %5 %56 %14
+ %51 = OpPhi %6 %9 %5 %28 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %18 %51 %17
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ %21 = OpSGreaterThan %18 %52 %17
+ OpSelectionMerge %23 None
+ OpBranchConditional %21 %22 %23
+ %22 = OpLabel
+ %26 = OpIAdd %6 %52 %25
+ OpStore %8 %26
+ OpBranch %23
+ %23 = OpLabel
+ %56 = OpPhi %6 %52 %12 %26 %22
+ OpBranch %14
+ %14 = OpLabel
+ %28 = OpIAdd %6 %51 %25
+ OpStore %10 %28
+ OpBranch %11
+ %13 = OpLabel
+ %30 = OpIAdd %6 %52 %25
+ OpStore %8 %30
+ OpStore %31 %9
+ OpBranch %32
+ %32 = OpLabel
+ %53 = OpPhi %6 %9 %13 %40 %35
+ OpLoopMerge %34 %35 None
+ OpBranch %36
+ %36 = OpLabel
+ %38 = OpSLessThan %18 %53 %17
+ OpBranchConditional %38 %33 %34
+ %33 = OpLabel
+ OpBranch %35
+ %35 = OpLabel
+ %40 = OpIAdd %6 %53 %25
+ OpStore %31 %40
+ OpBranch %32
+ %34 = OpLabel
+ OpStore %41 %9
+ OpBranch %42
+ %42 = OpLabel
+ %54 = OpPhi %6 %9 %34 %50 %45
+ OpLoopMerge %44 %45 None
+ OpBranch %46
+ %46 = OpLabel
+ %48 = OpSLessThan %18 %54 %17
+ OpBranchConditional %48 %43 %44
+ %43 = OpLabel
+ OpBranch %45
+ %45 = OpLabel
+ %50 = OpIAdd %6 %54 %25
+ OpStore %41 %50
+ OpBranch %42
+ %44 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+
+ EXPECT_FALSE(LoopFusion(context.get(), loop_0, loop_0).AreCompatible());
+ EXPECT_FALSE(LoopFusion(context.get(), loop_0, loop_2).AreCompatible());
+ EXPECT_FALSE(LoopFusion(context.get(), loop_0, loop_1).AreCompatible());
+ EXPECT_TRUE(LoopFusion(context.get(), loop_1, loop_2).AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 12
+#version 440 core
+void main() {
+ int j = 0;
+ int i = 0;
+ for (; i < 10; i++) {}
+ for (; j < 10; j++) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, CompatibleInitDeclaredBeforeLoops) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "j"
+ OpName %10 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 10
+ %18 = OpTypeBool
+ %21 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpBranch %11
+ %11 = OpLabel
+ %32 = OpPhi %6 %9 %5 %22 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %18 %32 %17
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ %22 = OpIAdd %6 %32 %21
+ OpStore %10 %22
+ OpBranch %11
+ %13 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %33 = OpPhi %6 %9 %13 %31 %26
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %18 %33 %17
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %31 = OpIAdd %6 %33 %21
+ OpStore %8 %31
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ EXPECT_TRUE(LoopFusion(context.get(), loops[0], loops[1]).AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 13 regenerate!
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ // Can't fuse, several induction variables
+ for (int j = 0; j < 10; j++) {
+ b[i] = a[i];
+ }
+ for (int i = 0, j = 0; i < 10; i++, j = j+2) {
+ }
+}
+
+*/
+TEST_F(FusionCompatibilityTest, SeveralInductionVariables) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "j"
+ OpName %23 "b"
+ OpName %25 "a"
+ OpName %33 "i"
+ OpName %34 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %31 = OpConstant %6 1
+ %48 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %33 = OpVariable %7 Function
+ %34 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %50 = OpPhi %6 %9 %5 %32 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %50 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %50
+ %28 = OpLoad %6 %27
+ %29 = OpAccessChain %7 %23 %50
+ OpStore %29 %28
+ OpBranch %13
+ %13 = OpLabel
+ %32 = OpIAdd %6 %50 %31
+ OpStore %8 %32
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %33 %9
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %52 = OpPhi %6 %9 %12 %49 %38
+ %51 = OpPhi %6 %9 %12 %46 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %51 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %44 = OpAccessChain %7 %25 %52
+ OpStore %44 %51
+ OpBranch %38
+ %38 = OpLabel
+ %46 = OpIAdd %6 %51 %31
+ OpStore %33 %46
+ %49 = OpIAdd %6 %52 %48
+ OpStore %34 %49
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ EXPECT_FALSE(LoopFusion(context.get(), loops[0], loops[1]).AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 14
+#version 440 core
+void main() {
+ // Fine
+ for (int i = 0; i < 10; i = i + 2) {}
+ for (int j = 0; j < 10; j = j + 2) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, CompatibleNonIncrementStep) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "j"
+ OpName %10 "i"
+ OpName %11 "i"
+ OpName %24 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %18 = OpConstant %6 10
+ %19 = OpTypeBool
+ %22 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %11 = OpVariable %7 Function
+ %24 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpStore %11 %9
+ OpBranch %12
+ %12 = OpLabel
+ %34 = OpPhi %6 %9 %5 %23 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %19 %34 %18
+ OpBranchConditional %20 %13 %14
+ %13 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ %23 = OpIAdd %6 %34 %22
+ OpStore %11 %23
+ OpBranch %12
+ %14 = OpLabel
+ OpStore %24 %9
+ OpBranch %25
+ %25 = OpLabel
+ %35 = OpPhi %6 %9 %14 %33 %28
+ OpLoopMerge %27 %28 None
+ OpBranch %29
+ %29 = OpLabel
+ %31 = OpSLessThan %19 %35 %18
+ OpBranchConditional %31 %26 %27
+ %26 = OpLabel
+ OpBranch %28
+ %28 = OpLabel
+ %33 = OpIAdd %6 %35 %22
+ OpStore %24 %33
+ OpBranch %25
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ EXPECT_TRUE(LoopFusion(context.get(), loops[0], loops[1]).AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 15
+#version 440 core
+
+int j = 0;
+
+void main() {
+ // Not compatible, unknown init for second.
+ for (int i = 0; i < 10; i = i + 2) {}
+ for (; j < 10; j = j + 2) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, UnknonInitForSecondLoop) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "j"
+ OpName %11 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Private %6
+ %8 = OpVariable %7 Private
+ %9 = OpConstant %6 0
+ %10 = OpTypePointer Function %6
+ %18 = OpConstant %6 10
+ %19 = OpTypeBool
+ %22 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %11 = OpVariable %10 Function
+ OpStore %8 %9
+ OpStore %11 %9
+ OpBranch %12
+ %12 = OpLabel
+ %33 = OpPhi %6 %9 %5 %23 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %19 %33 %18
+ OpBranchConditional %20 %13 %14
+ %13 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ %23 = OpIAdd %6 %33 %22
+ OpStore %11 %23
+ OpBranch %12
+ %14 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ OpLoopMerge %26 %27 None
+ OpBranch %28
+ %28 = OpLabel
+ %29 = OpLoad %6 %8
+ %30 = OpSLessThan %19 %29 %18
+ OpBranchConditional %30 %25 %26
+ %25 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %31 = OpLoad %6 %8
+ %32 = OpIAdd %6 %31 %22
+ OpStore %8 %32
+ OpBranch %24
+ %26 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ EXPECT_FALSE(LoopFusion(context.get(), loops[0], loops[1]).AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 16
+#version 440 core
+void main() {
+ // Not compatible, continue in loop 0
+ for (int i = 0; i < 10; ++i) {
+ if (i % 2 == 1) {
+ continue;
+ }
+ }
+ for (int j = 0; j < 10; ++j) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, Continue) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %29 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 2
+ %22 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %29 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %39 = OpPhi %6 %9 %5 %28 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %39 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %21 = OpSMod %6 %39 %20
+ %23 = OpIEqual %17 %21 %22
+ OpSelectionMerge %25 None
+ OpBranchConditional %23 %24 %25
+ %24 = OpLabel
+ OpBranch %13
+ %25 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %28 = OpIAdd %6 %39 %22
+ OpStore %8 %28
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %29 %9
+ OpBranch %30
+ %30 = OpLabel
+ %40 = OpPhi %6 %9 %12 %38 %33
+ OpLoopMerge %32 %33 None
+ OpBranch %34
+ %34 = OpLabel
+ %36 = OpSLessThan %17 %40 %16
+ OpBranchConditional %36 %31 %32
+ %31 = OpLabel
+ OpBranch %33
+ %33 = OpLabel
+ %38 = OpIAdd %6 %40 %22
+ OpStore %29 %38
+ OpBranch %30
+ %32 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ EXPECT_FALSE(LoopFusion(context.get(), loops[0], loops[1]).AreCompatible());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ // Compatible
+ for (int i = 0; i < 10; ++i) {
+ if (i % 2 == 1) {
+ } else {
+ a[i] = i;
+ }
+ }
+ for (int j = 0; j < 10; ++j) {}
+}
+
+*/
+TEST_F(FusionCompatibilityTest, IfElseInLoop) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %31 "a"
+ OpName %37 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 2
+ %22 = OpConstant %6 1
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %6 %28
+ %30 = OpTypePointer Function %29
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %31 = OpVariable %30 Function
+ %37 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %47 = OpPhi %6 %9 %5 %36 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %47 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %21 = OpSMod %6 %47 %20
+ %23 = OpIEqual %17 %21 %22
+ OpSelectionMerge %25 None
+ OpBranchConditional %23 %24 %26
+ %24 = OpLabel
+ OpBranch %25
+ %26 = OpLabel
+ %34 = OpAccessChain %7 %31 %47
+ OpStore %34 %47
+ OpBranch %25
+ %25 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %36 = OpIAdd %6 %47 %22
+ OpStore %8 %36
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %37 %9
+ OpBranch %38
+ %38 = OpLabel
+ %48 = OpPhi %6 %9 %12 %46 %41
+ OpLoopMerge %40 %41 None
+ OpBranch %42
+ %42 = OpLabel
+ %44 = OpSLessThan %17 %48 %16
+ OpBranchConditional %44 %39 %40
+ %39 = OpLabel
+ OpBranch %41
+ %41 = OpLabel
+ %46 = OpIAdd %6 %48 %22
+ OpStore %37 %46
+ OpBranch %38
+ %40 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ EXPECT_TRUE(LoopFusion(context.get(), loops[0], loops[1]).AreCompatible());
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/fusion_illegal.cpp b/test/opt/loop_optimizations/fusion_illegal.cpp
new file mode 100644
index 0000000..26d5445
--- /dev/null
+++ b/test/opt/loop_optimizations/fusion_illegal.cpp
@@ -0,0 +1,1592 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <algorithm>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/loop_fusion.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FusionIllegalTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Illegal, loop-independent dependence will become a
+ // backward loop-carried antidependence
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i+1] + 2;
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, PositiveDistanceCreatedRAW) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %34 "i"
+ OpName %42 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %48 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %53 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %53 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %53
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %53
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %53 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %54 = OpPhi %6 %9 %12 %52 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %54 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpIAdd %6 %54 %29
+ %46 = OpAccessChain %7 %23 %45
+ %47 = OpLoad %6 %46
+ %49 = OpIAdd %6 %47 %48
+ %50 = OpAccessChain %7 %42 %54
+ OpStore %50 %49
+ OpBranch %38
+ %38 = OpLabel
+ %52 = OpIAdd %6 %54 %29
+ OpStore %34 %52
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+
+int func() {
+ return 10;
+}
+
+void main() {
+ int[10] a;
+ int[10] b;
+ // Illegal, function call
+ for (int i = 0; i < 10; i++) {
+ a[i] = func();
+ }
+ for (int i = 0; i < 10; i++) {
+ b[i] = a[i];
+ }
+}
+*/
+TEST_F(FusionIllegalTest, FunctionCall) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "func("
+ OpName %14 "i"
+ OpName %28 "a"
+ OpName %35 "i"
+ OpName %43 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeFunction %6
+ %10 = OpConstant %6 10
+ %13 = OpTypePointer Function %6
+ %15 = OpConstant %6 0
+ %22 = OpTypeBool
+ %24 = OpTypeInt 32 0
+ %25 = OpConstant %24 10
+ %26 = OpTypeArray %6 %25
+ %27 = OpTypePointer Function %26
+ %33 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %14 = OpVariable %13 Function
+ %28 = OpVariable %27 Function
+ %35 = OpVariable %13 Function
+ %43 = OpVariable %27 Function
+ OpStore %14 %15
+ OpBranch %16
+ %16 = OpLabel
+ %51 = OpPhi %6 %15 %5 %34 %19
+ OpLoopMerge %18 %19 None
+ OpBranch %20
+ %20 = OpLabel
+ %23 = OpSLessThan %22 %51 %10
+ OpBranchConditional %23 %17 %18
+ %17 = OpLabel
+ %30 = OpFunctionCall %6 %8
+ %31 = OpAccessChain %13 %28 %51
+ OpStore %31 %30
+ OpBranch %19
+ %19 = OpLabel
+ %34 = OpIAdd %6 %51 %33
+ OpStore %14 %34
+ OpBranch %16
+ %18 = OpLabel
+ OpStore %35 %15
+ OpBranch %36
+ %36 = OpLabel
+ %52 = OpPhi %6 %15 %18 %50 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %22 %52 %10
+ OpBranchConditional %42 %37 %38
+ %37 = OpLabel
+ %46 = OpAccessChain %13 %28 %52
+ %47 = OpLoad %6 %46
+ %48 = OpAccessChain %13 %43 %52
+ OpStore %48 %47
+ OpBranch %39
+ %39 = OpLabel
+ %50 = OpIAdd %6 %52 %33
+ OpStore %35 %50
+ OpBranch %36
+ %38 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %6 None %7
+ %9 = OpLabel
+ OpReturnValue %10
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 16
+#version 440 core
+void main() {
+ int[10][10] a;
+ int[10][10] b;
+ int[10][10] c;
+ // Illegal outer.
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ c[i][j] = a[i][j] + 2;
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ b[i][j] = c[i+1][j] + 10;
+ }
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, PositiveDistanceCreatedRAWOuterLoop) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %32 "c"
+ OpName %35 "a"
+ OpName %48 "i"
+ OpName %56 "j"
+ OpName %64 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %6 %28
+ %30 = OpTypeArray %29 %28
+ %31 = OpTypePointer Function %30
+ %40 = OpConstant %6 2
+ %44 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %31 Function
+ %35 = OpVariable %31 Function
+ %48 = OpVariable %7 Function
+ %56 = OpVariable %7 Function
+ %64 = OpVariable %31 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %78 = OpPhi %6 %9 %5 %47 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %78 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %82 = OpPhi %6 %9 %11 %45 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %82 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %38 = OpAccessChain %7 %35 %78 %82
+ %39 = OpLoad %6 %38
+ %41 = OpIAdd %6 %39 %40
+ %42 = OpAccessChain %7 %32 %78 %82
+ OpStore %42 %41
+ OpBranch %23
+ %23 = OpLabel
+ %45 = OpIAdd %6 %82 %44
+ OpStore %19 %45
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %47 = OpIAdd %6 %78 %44
+ OpStore %8 %47
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %48 %9
+ OpBranch %49
+ %49 = OpLabel
+ %79 = OpPhi %6 %9 %12 %77 %52
+ OpLoopMerge %51 %52 None
+ OpBranch %53
+ %53 = OpLabel
+ %55 = OpSLessThan %17 %79 %16
+ OpBranchConditional %55 %50 %51
+ %50 = OpLabel
+ OpStore %56 %9
+ OpBranch %57
+ %57 = OpLabel
+ %80 = OpPhi %6 %9 %50 %75 %60
+ OpLoopMerge %59 %60 None
+ OpBranch %61
+ %61 = OpLabel
+ %63 = OpSLessThan %17 %80 %16
+ OpBranchConditional %63 %58 %59
+ %58 = OpLabel
+ %68 = OpIAdd %6 %79 %44
+ %70 = OpAccessChain %7 %32 %68 %80
+ %71 = OpLoad %6 %70
+ %72 = OpIAdd %6 %71 %16
+ %73 = OpAccessChain %7 %64 %79 %80
+ OpStore %73 %72
+ OpBranch %60
+ %60 = OpLabel
+ %75 = OpIAdd %6 %80 %44
+ OpStore %56 %75
+ OpBranch %57
+ %59 = OpLabel
+ OpBranch %52
+ %52 = OpLabel
+ %77 = OpIAdd %6 %79 %44
+ OpStore %48 %77
+ OpBranch %49
+ %51 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 4u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+ auto loop_3 = loops[3];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_2, loop_3);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 19
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Illegal, would create a backward loop-carried anti-dependence.
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ a[i+1] = c[i] + 2;
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, PositiveDistanceCreatedWAR) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "c"
+ OpName %25 "a"
+ OpName %34 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %47 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %52 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %52 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %52
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %52
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %52 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %53 = OpPhi %6 %9 %12 %51 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %53 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %43 = OpIAdd %6 %53 %29
+ %45 = OpAccessChain %7 %23 %53
+ %46 = OpLoad %6 %45
+ %48 = OpIAdd %6 %46 %47
+ %49 = OpAccessChain %7 %25 %43
+ OpStore %49 %48
+ OpBranch %38
+ %38 = OpLabel
+ %51 = OpIAdd %6 %53 %29
+ OpStore %34 %51
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 21
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Illegal, would create a backward loop-carried anti-dependence.
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ a[i+1] = c[i+1] + 2;
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, PositiveDistanceCreatedWAW) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %34 "i"
+ OpName %44 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %49 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %44 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %54 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %54 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %54
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %54
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %54 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %55 = OpPhi %6 %9 %12 %53 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %55 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %43 = OpIAdd %6 %55 %29
+ %46 = OpIAdd %6 %55 %29
+ %47 = OpAccessChain %7 %44 %46
+ %48 = OpLoad %6 %47
+ %50 = OpIAdd %6 %48 %49
+ %51 = OpAccessChain %7 %23 %43
+ OpStore %51 %50
+ OpBranch %38
+ %38 = OpLabel
+ %53 = OpIAdd %6 %55 %29
+ OpStore %34 %53
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 28
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ int sum_0 = 0;
+
+ // Illegal
+ for (int i = 0; i < 10; i++) {
+ sum_0 += a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ sum_0 += b[j];
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, SameReductionVariable) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "sum_0"
+ OpName %10 "i"
+ OpName %24 "a"
+ OpName %33 "j"
+ OpName %41 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 10
+ %18 = OpTypeBool
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 10
+ %22 = OpTypeArray %6 %21
+ %23 = OpTypePointer Function %22
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %24 = OpVariable %23 Function
+ %33 = OpVariable %7 Function
+ %41 = OpVariable %23 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpBranch %11
+ %11 = OpLabel
+ %52 = OpPhi %6 %9 %5 %29 %14
+ %49 = OpPhi %6 %9 %5 %32 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %18 %49 %17
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ %26 = OpAccessChain %7 %24 %49
+ %27 = OpLoad %6 %26
+ %29 = OpIAdd %6 %52 %27
+ OpStore %8 %29
+ OpBranch %14
+ %14 = OpLabel
+ %32 = OpIAdd %6 %49 %31
+ OpStore %10 %32
+ OpBranch %11
+ %13 = OpLabel
+ OpStore %33 %9
+ OpBranch %34
+ %34 = OpLabel
+ %51 = OpPhi %6 %52 %13 %46 %37
+ %50 = OpPhi %6 %9 %13 %48 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %40 = OpSLessThan %18 %50 %17
+ OpBranchConditional %40 %35 %36
+ %35 = OpLabel
+ %43 = OpAccessChain %7 %41 %50
+ %44 = OpLoad %6 %43
+ %46 = OpIAdd %6 %51 %44
+ OpStore %8 %46
+ OpBranch %37
+ %37 = OpLabel
+ %48 = OpIAdd %6 %50 %31
+ OpStore %33 %48
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 28
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ int sum_0 = 0;
+
+ // Illegal
+ for (int i = 0; i < 10; i++) {
+ sum_0 += a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ sum_0 += b[j];
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, SameReductionVariableLCSSA) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "sum_0"
+ OpName %10 "i"
+ OpName %24 "a"
+ OpName %33 "j"
+ OpName %41 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 10
+ %18 = OpTypeBool
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 10
+ %22 = OpTypeArray %6 %21
+ %23 = OpTypePointer Function %22
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %24 = OpVariable %23 Function
+ %33 = OpVariable %7 Function
+ %41 = OpVariable %23 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpBranch %11
+ %11 = OpLabel
+ %52 = OpPhi %6 %9 %5 %29 %14
+ %49 = OpPhi %6 %9 %5 %32 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %18 %49 %17
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ %26 = OpAccessChain %7 %24 %49
+ %27 = OpLoad %6 %26
+ %29 = OpIAdd %6 %52 %27
+ OpStore %8 %29
+ OpBranch %14
+ %14 = OpLabel
+ %32 = OpIAdd %6 %49 %31
+ OpStore %10 %32
+ OpBranch %11
+ %13 = OpLabel
+ OpStore %33 %9
+ OpBranch %34
+ %34 = OpLabel
+ %51 = OpPhi %6 %52 %13 %46 %37
+ %50 = OpPhi %6 %9 %13 %48 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %40 = OpSLessThan %18 %50 %17
+ OpBranchConditional %40 %35 %36
+ %35 = OpLabel
+ %43 = OpAccessChain %7 %41 %50
+ %44 = OpLoad %6 %43
+ %46 = OpIAdd %6 %51 %44
+ OpStore %8 %46
+ OpBranch %37
+ %37 = OpLabel
+ %48 = OpIAdd %6 %50 %31
+ OpStore %33 %48
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopUtils utils_0(context.get(), loops[0]);
+ utils_0.MakeLoopClosedSSA();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 30
+#version 440 core
+int x;
+void main() {
+ int[10] a;
+ int[10] b;
+
+ // Illegal, x is unknown.
+ for (int i = 0; i < 10; i++) {
+ a[x] = a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ a[j] = b[j];
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, UnknownIndexVariable) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "x"
+ OpName %34 "j"
+ OpName %43 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %24 = OpTypePointer Private %6
+ %25 = OpVariable %24 Private
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %43 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %50 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %50 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpLoad %6 %25
+ %28 = OpAccessChain %7 %23 %50
+ %29 = OpLoad %6 %28
+ %30 = OpAccessChain %7 %23 %26
+ OpStore %30 %29
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %50 %32
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %51 = OpPhi %6 %9 %12 %49 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %51 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %43 %51
+ %46 = OpLoad %6 %45
+ %47 = OpAccessChain %7 %23 %51
+ OpStore %47 %46
+ OpBranch %38
+ %38 = OpLabel
+ %49 = OpIAdd %6 %51 %32
+ OpStore %34 %49
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ int sum = 0;
+
+ // Illegal, accumulator used for indexing.
+ for (int i = 0; i < 10; i++) {
+ sum += a[i];
+ b[sum] = a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ b[j] = b[j]+1;
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, AccumulatorIndexing) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "sum"
+ OpName %10 "i"
+ OpName %24 "a"
+ OpName %30 "b"
+ OpName %39 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 10
+ %18 = OpTypeBool
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 10
+ %22 = OpTypeArray %6 %21
+ %23 = OpTypePointer Function %22
+ %37 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %24 = OpVariable %23 Function
+ %30 = OpVariable %23 Function
+ %39 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpBranch %11
+ %11 = OpLabel
+ %57 = OpPhi %6 %9 %5 %29 %14
+ %55 = OpPhi %6 %9 %5 %38 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %18 %55 %17
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ %26 = OpAccessChain %7 %24 %55
+ %27 = OpLoad %6 %26
+ %29 = OpIAdd %6 %57 %27
+ OpStore %8 %29
+ %33 = OpAccessChain %7 %24 %55
+ %34 = OpLoad %6 %33
+ %35 = OpAccessChain %7 %30 %29
+ OpStore %35 %34
+ OpBranch %14
+ %14 = OpLabel
+ %38 = OpIAdd %6 %55 %37
+ OpStore %10 %38
+ OpBranch %11
+ %13 = OpLabel
+ OpStore %39 %9
+ OpBranch %40
+ %40 = OpLabel
+ %56 = OpPhi %6 %9 %13 %54 %43
+ OpLoopMerge %42 %43 None
+ OpBranch %44
+ %44 = OpLabel
+ %46 = OpSLessThan %18 %56 %17
+ OpBranchConditional %46 %41 %42
+ %41 = OpLabel
+ %49 = OpAccessChain %7 %30 %56
+ %50 = OpLoad %6 %49
+ %51 = OpIAdd %6 %50 %37
+ %52 = OpAccessChain %7 %30 %56
+ OpStore %52 %51
+ OpBranch %43
+ %43 = OpLabel
+ %54 = OpIAdd %6 %56 %37
+ OpStore %39 %54
+ OpBranch %40
+ %42 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 33
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ // Illegal, barrier.
+ for (int i = 0; i < 10; i++) {
+ a[i] = a[i] * 2;
+ memoryBarrier();
+ }
+ for (int j = 0; j < 10; j++) {
+ b[j] = b[j] + 1;
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, Barrier) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %36 "j"
+ OpName %44 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %28 = OpConstant %6 2
+ %31 = OpConstant %19 1
+ %32 = OpConstant %19 3400
+ %34 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %36 = OpVariable %7 Function
+ %44 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %53 = OpPhi %6 %9 %5 %35 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %53 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpAccessChain %7 %23 %53
+ %27 = OpLoad %6 %26
+ %29 = OpIMul %6 %27 %28
+ %30 = OpAccessChain %7 %23 %53
+ OpStore %30 %29
+ OpMemoryBarrier %31 %32
+ OpBranch %13
+ %13 = OpLabel
+ %35 = OpIAdd %6 %53 %34
+ OpStore %8 %35
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %36 %9
+ OpBranch %37
+ %37 = OpLabel
+ %54 = OpPhi %6 %9 %12 %52 %40
+ OpLoopMerge %39 %40 None
+ OpBranch %41
+ %41 = OpLabel
+ %43 = OpSLessThan %17 %54 %16
+ OpBranchConditional %43 %38 %39
+ %38 = OpLabel
+ %47 = OpAccessChain %7 %44 %54
+ %48 = OpLoad %6 %47
+ %49 = OpIAdd %6 %48 %34
+ %50 = OpAccessChain %7 %44 %54
+ OpStore %50 %49
+ OpBranch %40
+ %40 = OpLabel
+ %52 = OpIAdd %6 %54 %34
+ OpStore %36 %52
+ OpBranch %37
+ %39 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+struct TestStruct {
+ int[10] a;
+ int b;
+};
+
+void main() {
+ TestStruct test_0;
+ TestStruct test_1;
+
+ for (int i = 0; i < 10; i++) {
+ test_0.a[i] = i;
+ }
+ for (int j = 0; j < 10; j++) {
+ test_0 = test_1;
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, ArrayInStruct) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "TestStruct"
+ OpMemberName %22 0 "a"
+ OpMemberName %22 1 "b"
+ OpName %24 "test_0"
+ OpName %31 "j"
+ OpName %39 "test_1"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypeStruct %21 %6
+ %23 = OpTypePointer Function %22
+ %29 = OpConstant %6 1
+ %47 = OpUndef %22
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %24 = OpVariable %23 Function
+ %31 = OpVariable %7 Function
+ %39 = OpVariable %23 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %43 = OpPhi %6 %9 %5 %30 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %43 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %24 %9 %43
+ OpStore %27 %43
+ OpBranch %13
+ %13 = OpLabel
+ %30 = OpIAdd %6 %43 %29
+ OpStore %8 %30
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %31 %9
+ OpBranch %32
+ %32 = OpLabel
+ %44 = OpPhi %6 %9 %12 %42 %35
+ OpLoopMerge %34 %35 None
+ OpBranch %36
+ %36 = OpLabel
+ %38 = OpSLessThan %17 %44 %16
+ OpBranchConditional %38 %33 %34
+ %33 = OpLabel
+ OpStore %24 %47
+ OpBranch %35
+ %35 = OpLabel
+ %42 = OpIAdd %6 %44 %29
+ OpStore %31 %42
+ OpBranch %32
+ %34 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 450
+
+struct P {float x,y,z;};
+uniform G { int a; P b[2]; int c; } g;
+layout(location = 0) out float o;
+
+void main()
+{
+ P p[2];
+ for (int i = 0; i < 2; ++i) {
+ p = g.b;
+ }
+ for (int j = 0; j < 2; ++j) {
+ o = p[g.a].x;
+ }
+}
+
+*/
+TEST_F(FusionIllegalTest, NestedAccessChain) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %64
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 450
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %20 "P"
+ OpMemberName %20 0 "x"
+ OpMemberName %20 1 "y"
+ OpMemberName %20 2 "z"
+ OpName %25 "p"
+ OpName %26 "P"
+ OpMemberName %26 0 "x"
+ OpMemberName %26 1 "y"
+ OpMemberName %26 2 "z"
+ OpName %28 "G"
+ OpMemberName %28 0 "a"
+ OpMemberName %28 1 "b"
+ OpMemberName %28 2 "c"
+ OpName %30 "g"
+ OpName %55 "j"
+ OpName %64 "o"
+ OpMemberDecorate %26 0 Offset 0
+ OpMemberDecorate %26 1 Offset 4
+ OpMemberDecorate %26 2 Offset 8
+ OpDecorate %27 ArrayStride 16
+ OpMemberDecorate %28 0 Offset 0
+ OpMemberDecorate %28 1 Offset 16
+ OpMemberDecorate %28 2 Offset 48
+ OpDecorate %28 Block
+ OpDecorate %30 DescriptorSet 0
+ OpDecorate %64 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 2
+ %17 = OpTypeBool
+ %19 = OpTypeFloat 32
+ %20 = OpTypeStruct %19 %19 %19
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 2
+ %23 = OpTypeArray %20 %22
+ %24 = OpTypePointer Function %23
+ %26 = OpTypeStruct %19 %19 %19
+ %27 = OpTypeArray %26 %22
+ %28 = OpTypeStruct %6 %27 %6
+ %29 = OpTypePointer Uniform %28
+ %30 = OpVariable %29 Uniform
+ %31 = OpConstant %6 1
+ %32 = OpTypePointer Uniform %27
+ %36 = OpTypePointer Function %20
+ %39 = OpTypePointer Function %19
+ %63 = OpTypePointer Output %19
+ %64 = OpVariable %63 Output
+ %65 = OpTypePointer Uniform %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %25 = OpVariable %24 Function
+ %55 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %72 = OpPhi %6 %9 %5 %54 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %72 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %33 = OpAccessChain %32 %30 %31
+ %34 = OpLoad %27 %33
+ %35 = OpCompositeExtract %26 %34 0
+ %37 = OpAccessChain %36 %25 %9
+ %38 = OpCompositeExtract %19 %35 0
+ %40 = OpAccessChain %39 %37 %9
+ OpStore %40 %38
+ %41 = OpCompositeExtract %19 %35 1
+ %42 = OpAccessChain %39 %37 %31
+ OpStore %42 %41
+ %43 = OpCompositeExtract %19 %35 2
+ %44 = OpAccessChain %39 %37 %16
+ OpStore %44 %43
+ %45 = OpCompositeExtract %26 %34 1
+ %46 = OpAccessChain %36 %25 %31
+ %47 = OpCompositeExtract %19 %45 0
+ %48 = OpAccessChain %39 %46 %9
+ OpStore %48 %47
+ %49 = OpCompositeExtract %19 %45 1
+ %50 = OpAccessChain %39 %46 %31
+ OpStore %50 %49
+ %51 = OpCompositeExtract %19 %45 2
+ %52 = OpAccessChain %39 %46 %16
+ OpStore %52 %51
+ OpBranch %13
+ %13 = OpLabel
+ %54 = OpIAdd %6 %72 %31
+ OpStore %8 %54
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %55 %9
+ OpBranch %56
+ %56 = OpLabel
+ %73 = OpPhi %6 %9 %12 %71 %59
+ OpLoopMerge %58 %59 None
+ OpBranch %60
+ %60 = OpLabel
+ %62 = OpSLessThan %17 %73 %16
+ OpBranchConditional %62 %57 %58
+ %57 = OpLabel
+ %66 = OpAccessChain %65 %30 %9
+ %67 = OpLoad %6 %66
+ %68 = OpAccessChain %39 %25 %67 %9
+ %69 = OpLoad %19 %68
+ OpStore %64 %69
+ OpBranch %59
+ %59 = OpLabel
+ %71 = OpIAdd %6 %73 %31
+ OpStore %55 %71
+ OpBranch %56
+ %58 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_FALSE(fusion.IsLegal());
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/fusion_legal.cpp b/test/opt/loop_optimizations/fusion_legal.cpp
new file mode 100644
index 0000000..41d796f
--- /dev/null
+++ b/test/opt/loop_optimizations/fusion_legal.cpp
@@ -0,0 +1,4578 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <algorithm>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/loop_fusion.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FusionLegalTest = PassTest<::testing::Test>;
+
+bool Validate(const std::vector<uint32_t>& bin) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {bin.data(), bin.size()};
+ spv_result_t error = spvValidate(spvContext, &binary, &diagnostic);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ return error == 0;
+}
+
+void Match(const std::string& checks, IRContext* context) {
+ // Silence unused warnings with !defined(SPIRV_EFFCE)
+ (void)checks;
+
+ std::vector<uint32_t> bin;
+ context->module()->ToBinary(&bin, true);
+ EXPECT_TRUE(Validate(bin));
+ std::string assembly;
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_2);
+ EXPECT_TRUE(
+ tools.Disassemble(bin, &assembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER))
+ << "Disassembling failed for shader:\n"
+ << assembly << std::endl;
+ auto match_result = effcee::Match(assembly, checks);
+ EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+ << match_result.message() << "\nChecking result:\n"
+ << assembly;
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ // No dependence, legal
+ for (int i = 0; i < 10; i++) {
+ a[i] = a[i]*2;
+ }
+ for (int i = 0; i < 10; i++) {
+ b[i] = b[i]+2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, DifferentArraysInLoops) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %34 "i"
+ OpName %42 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %28 = OpConstant %6 2
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpAccessChain %7 %23 %51
+ %27 = OpLoad %6 %26
+ %29 = OpIMul %6 %27 %28
+ %30 = OpAccessChain %7 %23 %51
+ OpStore %30 %29
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %51 %32
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %52 = OpPhi %6 %9 %12 %50 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %52 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %42 %52
+ %46 = OpLoad %6 %45
+ %47 = OpIAdd %6 %46 %28
+ %48 = OpAccessChain %7 %42 %52
+ OpStore %48 %47
+ OpBranch %38
+ %38 = OpLabel
+ %50 = OpIAdd %6 %52 %32
+ OpStore %34 %50
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Only loads to the same array, legal
+ for (int i = 0; i < 10; i++) {
+ b[i] = a[i]*2;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i]+2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, OnlyLoadsToSameArray) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "b"
+ OpName %25 "a"
+ OpName %35 "i"
+ OpName %43 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 2
+ %33 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %35 = OpVariable %7 Function
+ %43 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %52 = OpPhi %6 %9 %5 %34 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %52 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %52
+ %28 = OpLoad %6 %27
+ %30 = OpIMul %6 %28 %29
+ %31 = OpAccessChain %7 %23 %52
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %34 = OpIAdd %6 %52 %33
+ OpStore %8 %34
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %35 %9
+ OpBranch %36
+ %36 = OpLabel
+ %53 = OpPhi %6 %9 %12 %51 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %17 %53 %16
+ OpBranchConditional %42 %37 %38
+ %37 = OpLabel
+ %46 = OpAccessChain %7 %25 %53
+ %47 = OpLoad %6 %46
+ %48 = OpIAdd %6 %47 %29
+ %49 = OpAccessChain %7 %43 %53
+ OpStore %49 %48
+ OpBranch %39
+ %39 = OpLabel
+ %51 = OpIAdd %6 %53 %33
+ OpStore %35 %51
+ OpBranch %36
+ %38 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ // No loop-carried dependences, legal
+ for (int i = 0; i < 10; i++) {
+ a[i] = a[i]*2;
+ }
+ for (int i = 0; i < 10; i++) {
+ b[i] = a[i]+2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, NoLoopCarriedDependences) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %34 "i"
+ OpName %42 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %28 = OpConstant %6 2
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpAccessChain %7 %23 %51
+ %27 = OpLoad %6 %26
+ %29 = OpIMul %6 %27 %28
+ %30 = OpAccessChain %7 %23 %51
+ OpStore %30 %29
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %51 %32
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %52 = OpPhi %6 %9 %12 %50 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %52 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %23 %52
+ %46 = OpLoad %6 %45
+ %47 = OpIAdd %6 %46 %28
+ %48 = OpAccessChain %7 %42 %52
+ OpStore %48 %47
+ OpBranch %38
+ %38 = OpLabel
+ %50 = OpIAdd %6 %52 %32
+ OpStore %34 %50
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Parallelism inhibiting, but legal.
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i] + c[i-1];
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, ExistingLoopCarriedDependence) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %34 "i"
+ OpName %42 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %55 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %55 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %55
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %55
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %55 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %56 = OpPhi %6 %9 %12 %54 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %56 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %23 %56
+ %46 = OpLoad %6 %45
+ %48 = OpISub %6 %56 %29
+ %49 = OpAccessChain %7 %42 %48
+ %50 = OpLoad %6 %49
+ %51 = OpIAdd %6 %46 %50
+ %52 = OpAccessChain %7 %42 %56
+ OpStore %52 %51
+ OpBranch %38
+ %38 = OpLabel
+ %54 = OpIAdd %6 %56 %29
+ OpStore %34 %54
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[I_1:%\w+]] = OpISub {{%\w+}} [[PHI]] {{%\w+}}
+CHECK-NEXT: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Creates a loop-carried dependence, but negative, so legal
+ for (int i = 0; i < 10; i++) {
+ a[i+1] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, NegativeDistanceCreatedRAW) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %27 "b"
+ OpName %35 "i"
+ OpName %43 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %25 = OpConstant %6 1
+ %48 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %27 = OpVariable %22 Function
+ %35 = OpVariable %7 Function
+ %43 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %53 = OpPhi %6 %9 %5 %34 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %53 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpIAdd %6 %53 %25
+ %29 = OpAccessChain %7 %27 %53
+ %30 = OpLoad %6 %29
+ %31 = OpIAdd %6 %30 %25
+ %32 = OpAccessChain %7 %23 %26
+ OpStore %32 %31
+ OpBranch %13
+ %13 = OpLabel
+ %34 = OpIAdd %6 %53 %25
+ OpStore %8 %34
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %35 %9
+ OpBranch %36
+ %36 = OpLabel
+ %54 = OpPhi %6 %9 %12 %52 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %17 %54 %16
+ OpBranchConditional %42 %37 %38
+ %37 = OpLabel
+ %46 = OpAccessChain %7 %23 %54
+ %47 = OpLoad %6 %46
+ %49 = OpIAdd %6 %47 %48
+ %50 = OpAccessChain %7 %43 %54
+ OpStore %50 %49
+ OpBranch %39
+ %39 = OpLabel
+ %52 = OpIAdd %6 %54 %25
+ OpStore %35 %52
+ OpBranch %36
+ %38 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+
+ {
+ auto& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Legal
+ for (int i = 0; i < 10; i++) {
+ a[i+1] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i+1] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, NoLoopCarriedDependencesAdjustedIndex) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %27 "b"
+ OpName %35 "i"
+ OpName %43 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %25 = OpConstant %6 1
+ %49 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %27 = OpVariable %22 Function
+ %35 = OpVariable %7 Function
+ %43 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %54 = OpPhi %6 %9 %5 %34 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %54 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpIAdd %6 %54 %25
+ %29 = OpAccessChain %7 %27 %54
+ %30 = OpLoad %6 %29
+ %31 = OpIAdd %6 %30 %25
+ %32 = OpAccessChain %7 %23 %26
+ OpStore %32 %31
+ OpBranch %13
+ %13 = OpLabel
+ %34 = OpIAdd %6 %54 %25
+ OpStore %8 %34
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %35 %9
+ OpBranch %36
+ %36 = OpLabel
+ %55 = OpPhi %6 %9 %12 %53 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %17 %55 %16
+ OpBranchConditional %42 %37 %38
+ %37 = OpLabel
+ %46 = OpIAdd %6 %55 %25
+ %47 = OpAccessChain %7 %23 %46
+ %48 = OpLoad %6 %47
+ %50 = OpIAdd %6 %48 %49
+ %51 = OpAccessChain %7 %43 %55
+ OpStore %51 %50
+ OpBranch %39
+ %39 = OpLabel
+ %53 = OpIAdd %6 %55 %25
+ OpStore %35 %53
+ OpBranch %36
+ %38 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Legal, independent locations in |a|, SIV
+ for (int i = 0; i < 10; i++) {
+ a[2*i+1] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[2*i] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, IndependentSIV) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %29 "b"
+ OpName %37 "i"
+ OpName %45 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %24 = OpConstant %6 2
+ %27 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %29 = OpVariable %22 Function
+ %37 = OpVariable %7 Function
+ %45 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %55 = OpPhi %6 %9 %5 %36 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %55 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpIMul %6 %24 %55
+ %28 = OpIAdd %6 %26 %27
+ %31 = OpAccessChain %7 %29 %55
+ %32 = OpLoad %6 %31
+ %33 = OpIAdd %6 %32 %27
+ %34 = OpAccessChain %7 %23 %28
+ OpStore %34 %33
+ OpBranch %13
+ %13 = OpLabel
+ %36 = OpIAdd %6 %55 %27
+ OpStore %8 %36
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %37 %9
+ OpBranch %38
+ %38 = OpLabel
+ %56 = OpPhi %6 %9 %12 %54 %41
+ OpLoopMerge %40 %41 None
+ OpBranch %42
+ %42 = OpLabel
+ %44 = OpSLessThan %17 %56 %16
+ OpBranchConditional %44 %39 %40
+ %39 = OpLabel
+ %48 = OpIMul %6 %24 %56
+ %49 = OpAccessChain %7 %23 %48
+ %50 = OpLoad %6 %49
+ %51 = OpIAdd %6 %50 %24
+ %52 = OpAccessChain %7 %45 %56
+ OpStore %52 %51
+ OpBranch %41
+ %41 = OpLabel
+ %54 = OpIAdd %6 %56 %27
+ OpStore %37 %54
+ OpBranch %38
+ %40 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[I_2:%\w+]] = OpIMul {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[I_2_1:%\w+]] = OpIAdd {{%\w+}} [[I_2]] {{%\w+}}
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_2_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[I_2:%\w+]] = OpIMul {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_2]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Legal, independent locations in |a|, ZIV
+ for (int i = 0; i < 10; i++) {
+ a[1] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[9] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, IndependentZIV) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %33 "i"
+ OpName %41 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %24 = OpConstant %6 1
+ %43 = OpConstant %6 9
+ %46 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %33 = OpVariable %7 Function
+ %41 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %32 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %51
+ %28 = OpLoad %6 %27
+ %29 = OpIAdd %6 %28 %24
+ %30 = OpAccessChain %7 %23 %24
+ OpStore %30 %29
+ OpBranch %13
+ %13 = OpLabel
+ %32 = OpIAdd %6 %51 %24
+ OpStore %8 %32
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %33 %9
+ OpBranch %34
+ %34 = OpLabel
+ %52 = OpPhi %6 %9 %12 %50 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %40 = OpSLessThan %17 %52 %16
+ OpBranchConditional %40 %35 %36
+ %35 = OpLabel
+ %44 = OpAccessChain %7 %23 %43
+ %45 = OpLoad %6 %44
+ %47 = OpIAdd %6 %45 %46
+ %48 = OpAccessChain %7 %41 %52
+ OpStore %48 %47
+ OpBranch %37
+ %37 = OpLabel
+ %50 = OpIAdd %6 %52 %24
+ OpStore %33 %50
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK-NOT: OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK: OpStore
+CHECK-NOT: OpPhi
+CHECK-NOT: OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK: OpLoad
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[20] a;
+ int[10] b;
+ int[10] c;
+ // Legal, non-overlapping sections in |a|
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i+10] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, NonOverlappingAccesses) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %28 "b"
+ OpName %37 "i"
+ OpName %45 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 20
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %25 = OpConstant %19 10
+ %26 = OpTypeArray %6 %25
+ %27 = OpTypePointer Function %26
+ %32 = OpConstant %6 1
+ %51 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %28 = OpVariable %27 Function
+ %37 = OpVariable %7 Function
+ %45 = OpVariable %27 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %56 = OpPhi %6 %9 %5 %36 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %56 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %30 = OpAccessChain %7 %28 %56
+ %31 = OpLoad %6 %30
+ %33 = OpIAdd %6 %31 %32
+ %34 = OpAccessChain %7 %23 %56
+ OpStore %34 %33
+ OpBranch %13
+ %13 = OpLabel
+ %36 = OpIAdd %6 %56 %32
+ OpStore %8 %36
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %37 %9
+ OpBranch %38
+ %38 = OpLabel
+ %57 = OpPhi %6 %9 %12 %55 %41
+ OpLoopMerge %40 %41 None
+ OpBranch %42
+ %42 = OpLabel
+ %44 = OpSLessThan %17 %57 %16
+ OpBranchConditional %44 %39 %40
+ %39 = OpLabel
+ %48 = OpIAdd %6 %57 %16
+ %49 = OpAccessChain %7 %23 %48
+ %50 = OpLoad %6 %49
+ %52 = OpIAdd %6 %50 %51
+ %53 = OpAccessChain %7 %45 %57
+ OpStore %53 %52
+ OpBranch %41
+ %41 = OpLabel
+ %55 = OpIAdd %6 %57 %32
+ OpStore %37 %55
+ OpBranch %38
+ %40 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NOT: OpPhi
+CHECK: [[I_10:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_10]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+)";
+
+ Match(checks, context.get());
+
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Legal, 3 adjacent loops
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i] + 2;
+ }
+ for (int i = 0; i < 10; i++) {
+ b[i] = c[i] + 10;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, AdjacentLoops) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %34 "i"
+ OpName %42 "c"
+ OpName %52 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %47 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ %52 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %68 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %68 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %68
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %68
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %68 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %69 = OpPhi %6 %9 %12 %51 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %69 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %23 %69
+ %46 = OpLoad %6 %45
+ %48 = OpIAdd %6 %46 %47
+ %49 = OpAccessChain %7 %42 %69
+ OpStore %49 %48
+ OpBranch %38
+ %38 = OpLabel
+ %51 = OpIAdd %6 %69 %29
+ OpStore %34 %51
+ OpBranch %35
+ %37 = OpLabel
+ OpStore %52 %9
+ OpBranch %53
+ %53 = OpLabel
+ %70 = OpPhi %6 %9 %37 %67 %56
+ OpLoopMerge %55 %56 None
+ OpBranch %57
+ %57 = OpLabel
+ %59 = OpSLessThan %17 %70 %16
+ OpBranchConditional %59 %54 %55
+ %54 = OpLabel
+ %62 = OpAccessChain %7 %42 %70
+ %63 = OpLoad %6 %62
+ %64 = OpIAdd %6 %63 %16
+ %65 = OpAccessChain %7 %25 %70
+ OpStore %65 %64
+ OpBranch %56
+ %56 = OpLabel
+ %67 = OpIAdd %6 %70 %29
+ OpStore %52 %67
+ OpBranch %53
+ %55 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[1], loops[2]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_1]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]]
+CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_2]]
+ )";
+
+ Match(checks, context.get());
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ std::string checks_ = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]]
+CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_2]]
+ )";
+
+ Match(checks_, context.get());
+
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10][10] a;
+ int[10][10] b;
+ int[10][10] c;
+ // Legal inner loop fusion
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ c[i][j] = a[i][j] + 2;
+ }
+ for (int j = 0; j < 10; j++) {
+ b[i][j] = c[i][j] + 10;
+ }
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, InnerLoopFusion) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %32 "c"
+ OpName %35 "a"
+ OpName %46 "j"
+ OpName %54 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %6 %28
+ %30 = OpTypeArray %29 %28
+ %31 = OpTypePointer Function %30
+ %40 = OpConstant %6 2
+ %44 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %31 Function
+ %35 = OpVariable %31 Function
+ %46 = OpVariable %7 Function
+ %54 = OpVariable %31 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %67 = OpPhi %6 %9 %5 %66 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %67 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %68 = OpPhi %6 %9 %11 %45 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %68 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %38 = OpAccessChain %7 %35 %67 %68
+ %39 = OpLoad %6 %38
+ %41 = OpIAdd %6 %39 %40
+ %42 = OpAccessChain %7 %32 %67 %68
+ OpStore %42 %41
+ OpBranch %23
+ %23 = OpLabel
+ %45 = OpIAdd %6 %68 %44
+ OpStore %19 %45
+ OpBranch %20
+ %22 = OpLabel
+ OpStore %46 %9
+ OpBranch %47
+ %47 = OpLabel
+ %69 = OpPhi %6 %9 %22 %64 %50
+ OpLoopMerge %49 %50 None
+ OpBranch %51
+ %51 = OpLabel
+ %53 = OpSLessThan %17 %69 %16
+ OpBranchConditional %53 %48 %49
+ %48 = OpLabel
+ %59 = OpAccessChain %7 %32 %67 %69
+ %60 = OpLoad %6 %59
+ %61 = OpIAdd %6 %60 %16
+ %62 = OpAccessChain %7 %54 %67 %69
+ OpStore %62 %61
+ OpBranch %50
+ %50 = OpLabel
+ %64 = OpIAdd %6 %69 %44
+ OpStore %46 %64
+ OpBranch %47
+ %49 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %66 = OpIAdd %6 %67 %44
+ OpStore %8 %66
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+
+ auto& ld_final = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld_final.NumLoops(), 2u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// 12
+#version 440 core
+void main() {
+ int[10][10] a;
+ int[10][10] b;
+ int[10][10] c;
+ // Legal both
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ c[i][j] = a[i][j] + 2;
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ b[i][j] = c[i][j] + 10;
+ }
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, OuterAndInnerLoop) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %32 "c"
+ OpName %35 "a"
+ OpName %48 "i"
+ OpName %56 "j"
+ OpName %64 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %6 %28
+ %30 = OpTypeArray %29 %28
+ %31 = OpTypePointer Function %30
+ %40 = OpConstant %6 2
+ %44 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %31 Function
+ %35 = OpVariable %31 Function
+ %48 = OpVariable %7 Function
+ %56 = OpVariable %7 Function
+ %64 = OpVariable %31 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %77 = OpPhi %6 %9 %5 %47 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %77 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %81 = OpPhi %6 %9 %11 %45 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %81 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %38 = OpAccessChain %7 %35 %77 %81
+ %39 = OpLoad %6 %38
+ %41 = OpIAdd %6 %39 %40
+ %42 = OpAccessChain %7 %32 %77 %81
+ OpStore %42 %41
+ OpBranch %23
+ %23 = OpLabel
+ %45 = OpIAdd %6 %81 %44
+ OpStore %19 %45
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %47 = OpIAdd %6 %77 %44
+ OpStore %8 %47
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %48 %9
+ OpBranch %49
+ %49 = OpLabel
+ %78 = OpPhi %6 %9 %12 %76 %52
+ OpLoopMerge %51 %52 None
+ OpBranch %53
+ %53 = OpLabel
+ %55 = OpSLessThan %17 %78 %16
+ OpBranchConditional %55 %50 %51
+ %50 = OpLabel
+ OpStore %56 %9
+ OpBranch %57
+ %57 = OpLabel
+ %79 = OpPhi %6 %9 %50 %74 %60
+ OpLoopMerge %59 %60 None
+ OpBranch %61
+ %61 = OpLabel
+ %63 = OpSLessThan %17 %79 %16
+ OpBranchConditional %63 %58 %59
+ %58 = OpLabel
+ %69 = OpAccessChain %7 %32 %78 %79
+ %70 = OpLoad %6 %69
+ %71 = OpIAdd %6 %70 %16
+ %72 = OpAccessChain %7 %64 %78 %79
+ OpStore %72 %71
+ OpBranch %60
+ %60 = OpLabel
+ %74 = OpIAdd %6 %79 %44
+ OpStore %56 %74
+ OpBranch %57
+ %59 = OpLabel
+ OpBranch %52
+ %52 = OpLabel
+ %76 = OpIAdd %6 %78 %44
+ OpStore %48 %76
+ OpBranch %49
+ %51 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 4u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+ auto loop_3 = loops[3];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_2, loop_3);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_3);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK: [[PHI_2:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+
+ {
+ auto& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+
+ {
+ auto& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10][10] a;
+ int[10][10] b;
+ int[10][10] c;
+ // Legal both, more complex
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ if (i % 2 == 0 && j % 2 == 0) {
+ c[i][j] = a[i][j] + 2;
+ }
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ b[i][j] = c[i][j] + 10;
+ }
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, OuterAndInnerLoopMoreComplex) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %44 "c"
+ OpName %47 "a"
+ OpName %59 "i"
+ OpName %67 "j"
+ OpName %75 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %28 = OpConstant %6 2
+ %39 = OpTypeInt 32 0
+ %40 = OpConstant %39 10
+ %41 = OpTypeArray %6 %40
+ %42 = OpTypeArray %41 %40
+ %43 = OpTypePointer Function %42
+ %55 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %44 = OpVariable %43 Function
+ %47 = OpVariable %43 Function
+ %59 = OpVariable %7 Function
+ %67 = OpVariable %7 Function
+ %75 = OpVariable %43 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %88 = OpPhi %6 %9 %5 %58 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %88 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %92 = OpPhi %6 %9 %11 %56 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %92 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %29 = OpSMod %6 %88 %28
+ %30 = OpIEqual %17 %29 %9
+ OpSelectionMerge %32 None
+ OpBranchConditional %30 %31 %32
+ %31 = OpLabel
+ %34 = OpSMod %6 %92 %28
+ %35 = OpIEqual %17 %34 %9
+ OpBranch %32
+ %32 = OpLabel
+ %36 = OpPhi %17 %30 %21 %35 %31
+ OpSelectionMerge %38 None
+ OpBranchConditional %36 %37 %38
+ %37 = OpLabel
+ %50 = OpAccessChain %7 %47 %88 %92
+ %51 = OpLoad %6 %50
+ %52 = OpIAdd %6 %51 %28
+ %53 = OpAccessChain %7 %44 %88 %92
+ OpStore %53 %52
+ OpBranch %38
+ %38 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %56 = OpIAdd %6 %92 %55
+ OpStore %19 %56
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %58 = OpIAdd %6 %88 %55
+ OpStore %8 %58
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %59 %9
+ OpBranch %60
+ %60 = OpLabel
+ %89 = OpPhi %6 %9 %12 %87 %63
+ OpLoopMerge %62 %63 None
+ OpBranch %64
+ %64 = OpLabel
+ %66 = OpSLessThan %17 %89 %16
+ OpBranchConditional %66 %61 %62
+ %61 = OpLabel
+ OpStore %67 %9
+ OpBranch %68
+ %68 = OpLabel
+ %90 = OpPhi %6 %9 %61 %85 %71
+ OpLoopMerge %70 %71 None
+ OpBranch %72
+ %72 = OpLabel
+ %74 = OpSLessThan %17 %90 %16
+ OpBranchConditional %74 %69 %70
+ %69 = OpLabel
+ %80 = OpAccessChain %7 %44 %89 %90
+ %81 = OpLoad %6 %80
+ %82 = OpIAdd %6 %81 %16
+ %83 = OpAccessChain %7 %75 %89 %90
+ OpStore %83 %82
+ OpBranch %71
+ %71 = OpLabel
+ %85 = OpIAdd %6 %90 %55
+ OpStore %67 %85
+ OpBranch %68
+ %70 = OpLabel
+ OpBranch %63
+ %63 = OpLabel
+ %87 = OpIAdd %6 %89 %55
+ OpStore %59 %87
+ OpBranch %60
+ %62 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 4u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+ auto loop_3 = loops[3];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_2, loop_3);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_3);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: OpPhi
+CHECK-NEXT: OpSelectionMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK: [[PHI_2:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: OpPhi
+CHECK-NEXT: OpSelectionMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10][10] a;
+ int[10][10] b;
+ int[10][10] c;
+ // Outer would have been illegal to fuse, but since written
+ // like this, inner loop fusion is legal.
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ c[i][j] = a[i][j] + 2;
+ }
+ for (int j = 0; j < 10; j++) {
+ b[i][j] = c[i+1][j] + 10;
+ }
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, InnerWithExistingDependenceOnOuter) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %32 "c"
+ OpName %35 "a"
+ OpName %46 "j"
+ OpName %54 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %6 %28
+ %30 = OpTypeArray %29 %28
+ %31 = OpTypePointer Function %30
+ %40 = OpConstant %6 2
+ %44 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %31 Function
+ %35 = OpVariable %31 Function
+ %46 = OpVariable %7 Function
+ %54 = OpVariable %31 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %68 = OpPhi %6 %9 %5 %67 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %68 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %69 = OpPhi %6 %9 %11 %45 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %69 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %38 = OpAccessChain %7 %35 %68 %69
+ %39 = OpLoad %6 %38
+ %41 = OpIAdd %6 %39 %40
+ %42 = OpAccessChain %7 %32 %68 %69
+ OpStore %42 %41
+ OpBranch %23
+ %23 = OpLabel
+ %45 = OpIAdd %6 %69 %44
+ OpStore %19 %45
+ OpBranch %20
+ %22 = OpLabel
+ OpStore %46 %9
+ OpBranch %47
+ %47 = OpLabel
+ %70 = OpPhi %6 %9 %22 %65 %50
+ OpLoopMerge %49 %50 None
+ OpBranch %51
+ %51 = OpLabel
+ %53 = OpSLessThan %17 %70 %16
+ OpBranchConditional %53 %48 %49
+ %48 = OpLabel
+ %58 = OpIAdd %6 %68 %44
+ %60 = OpAccessChain %7 %32 %58 %70
+ %61 = OpLoad %6 %60
+ %62 = OpIAdd %6 %61 %16
+ %63 = OpAccessChain %7 %54 %68 %70
+ OpStore %63 %62
+ OpBranch %50
+ %50 = OpLabel
+ %65 = OpIAdd %6 %70 %44
+ OpStore %46 %65
+ OpBranch %47
+ %49 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %67 = OpIAdd %6 %68 %44
+ OpStore %8 %67
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI_0]] {{%\w+}}
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // One dimensional arrays. Legal, outer dist 0, inner independent.
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ c[i] = a[j] + 2;
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ b[j] = c[i] + 10;
+ }
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, OuterAndInnerLoopOneDimArrays) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %31 "c"
+ OpName %33 "a"
+ OpName %45 "i"
+ OpName %53 "j"
+ OpName %61 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %6 %28
+ %30 = OpTypePointer Function %29
+ %37 = OpConstant %6 2
+ %41 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %31 = OpVariable %30 Function
+ %33 = OpVariable %30 Function
+ %45 = OpVariable %7 Function
+ %53 = OpVariable %7 Function
+ %61 = OpVariable %30 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %72 = OpPhi %6 %9 %5 %44 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %72 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %76 = OpPhi %6 %9 %11 %42 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %76 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %35 = OpAccessChain %7 %33 %76
+ %36 = OpLoad %6 %35
+ %38 = OpIAdd %6 %36 %37
+ %39 = OpAccessChain %7 %31 %72
+ OpStore %39 %38
+ OpBranch %23
+ %23 = OpLabel
+ %42 = OpIAdd %6 %76 %41
+ OpStore %19 %42
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %44 = OpIAdd %6 %72 %41
+ OpStore %8 %44
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %45 %9
+ OpBranch %46
+ %46 = OpLabel
+ %73 = OpPhi %6 %9 %12 %71 %49
+ OpLoopMerge %48 %49 None
+ OpBranch %50
+ %50 = OpLabel
+ %52 = OpSLessThan %17 %73 %16
+ OpBranchConditional %52 %47 %48
+ %47 = OpLabel
+ OpStore %53 %9
+ OpBranch %54
+ %54 = OpLabel
+ %74 = OpPhi %6 %9 %47 %69 %57
+ OpLoopMerge %56 %57 None
+ OpBranch %58
+ %58 = OpLabel
+ %60 = OpSLessThan %17 %74 %16
+ OpBranchConditional %60 %55 %56
+ %55 = OpLabel
+ %64 = OpAccessChain %7 %31 %73
+ %65 = OpLoad %6 %64
+ %66 = OpIAdd %6 %65 %16
+ %67 = OpAccessChain %7 %61 %74
+ OpStore %67 %66
+ OpBranch %57
+ %57 = OpLabel
+ %69 = OpIAdd %6 %74 %41
+ OpStore %53 %69
+ OpBranch %54
+ %56 = OpLabel
+ OpBranch %49
+ %49 = OpLabel
+ %71 = OpIAdd %6 %73 %41
+ OpStore %45 %71
+ OpBranch %46
+ %48 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 4u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+ auto loop_3 = loops[3];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_2, loop_3);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK: [[PHI_2:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_2]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ auto loop_0 = loops[0];
+ auto loop_1 = loops[1];
+ auto loop_2 = loops[2];
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_1);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_0, loop_2);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ {
+ LoopFusion fusion(context.get(), loop_1, loop_2);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Legal, creates a loop-carried dependence, but has negative distance
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i+1] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ a[i] = c[i] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, NegativeDistanceCreatedWAR) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "c"
+ OpName %25 "a"
+ OpName %35 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %27 = OpConstant %6 1
+ %47 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %35 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %52 = OpPhi %6 %9 %5 %34 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %52 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %28 = OpIAdd %6 %52 %27
+ %29 = OpAccessChain %7 %25 %28
+ %30 = OpLoad %6 %29
+ %31 = OpIAdd %6 %30 %27
+ %32 = OpAccessChain %7 %23 %52
+ OpStore %32 %31
+ OpBranch %13
+ %13 = OpLabel
+ %34 = OpIAdd %6 %52 %27
+ OpStore %8 %34
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %35 %9
+ OpBranch %36
+ %36 = OpLabel
+ %53 = OpPhi %6 %9 %12 %51 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %17 %53 %16
+ OpBranchConditional %42 %37 %38
+ %37 = OpLabel
+ %45 = OpAccessChain %7 %23 %53
+ %46 = OpLoad %6 %45
+ %48 = OpIAdd %6 %46 %47
+ %49 = OpAccessChain %7 %25 %53
+ OpStore %49 %48
+ OpBranch %39
+ %39 = OpLabel
+ %51 = OpIAdd %6 %53 %27
+ OpStore %35 %51
+ OpBranch %36
+ %38 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK-NEXT: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+
+ {
+ auto& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Legal, creates a loop-carried dependence, but has negative distance
+ for (int i = 0; i < 10; i++) {
+ a[i+1] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ a[i] = c[i+1] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, NegativeDistanceCreatedWAW) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %27 "b"
+ OpName %35 "i"
+ OpName %44 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %25 = OpConstant %6 1
+ %49 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %27 = OpVariable %22 Function
+ %35 = OpVariable %7 Function
+ %44 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %54 = OpPhi %6 %9 %5 %34 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %54 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpIAdd %6 %54 %25
+ %29 = OpAccessChain %7 %27 %54
+ %30 = OpLoad %6 %29
+ %31 = OpIAdd %6 %30 %25
+ %32 = OpAccessChain %7 %23 %26
+ OpStore %32 %31
+ OpBranch %13
+ %13 = OpLabel
+ %34 = OpIAdd %6 %54 %25
+ OpStore %8 %34
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %35 %9
+ OpBranch %36
+ %36 = OpLabel
+ %55 = OpPhi %6 %9 %12 %53 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %17 %55 %16
+ OpBranchConditional %42 %37 %38
+ %37 = OpLabel
+ %46 = OpIAdd %6 %55 %25
+ %47 = OpAccessChain %7 %44 %46
+ %48 = OpLoad %6 %47
+ %50 = OpIAdd %6 %48 %49
+ %51 = OpAccessChain %7 %23 %55
+ OpStore %51 %50
+ OpBranch %39
+ %39 = OpLabel
+ %53 = OpIAdd %6 %55 %25
+ OpStore %35 %53
+ OpBranch %36
+ %38 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpStore
+CHECK-NOT: OpPhi
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Legal, no loop-carried dependence
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ a[i] = c[i+1] + 2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, NoLoopCarriedDependencesWAW) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %34 "i"
+ OpName %43 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %48 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %43 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %53 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %53 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %53
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %53
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %53 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %54 = OpPhi %6 %9 %12 %52 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %54 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpIAdd %6 %54 %29
+ %46 = OpAccessChain %7 %43 %45
+ %47 = OpLoad %6 %46
+ %49 = OpIAdd %6 %47 %48
+ %50 = OpAccessChain %7 %23 %54
+ OpStore %50 %49
+ OpBranch %38
+ %38 = OpLabel
+ %52 = OpIAdd %6 %54 %29
+ OpStore %34 %52
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}}
+CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10][10] a;
+ int[10][10] b;
+ int[10][10] c;
+ // Legal outer. Continue and break are fine if nested in inner loops
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ if (j % 2 == 0) {
+ c[i][j] = a[i][j] + 2;
+ } else {
+ continue;
+ }
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ if (j % 2 == 0) {
+ b[i][j] = c[i][j] + 10;
+ } else {
+ break;
+ }
+ }
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, OuterloopWithBreakContinueInInner) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %38 "c"
+ OpName %41 "a"
+ OpName %55 "i"
+ OpName %63 "j"
+ OpName %76 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %28 = OpConstant %6 2
+ %33 = OpTypeInt 32 0
+ %34 = OpConstant %33 10
+ %35 = OpTypeArray %6 %34
+ %36 = OpTypeArray %35 %34
+ %37 = OpTypePointer Function %36
+ %51 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %38 = OpVariable %37 Function
+ %41 = OpVariable %37 Function
+ %55 = OpVariable %7 Function
+ %63 = OpVariable %7 Function
+ %76 = OpVariable %37 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %91 = OpPhi %6 %9 %5 %54 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %91 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %96 = OpPhi %6 %9 %11 %52 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %96 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %29 = OpSMod %6 %96 %28
+ %30 = OpIEqual %17 %29 %9
+ OpSelectionMerge %23 None
+ OpBranchConditional %30 %31 %48
+ %31 = OpLabel
+ %44 = OpAccessChain %7 %41 %91 %96
+ %45 = OpLoad %6 %44
+ %46 = OpIAdd %6 %45 %28
+ %47 = OpAccessChain %7 %38 %91 %96
+ OpStore %47 %46
+ OpBranch %32
+ %48 = OpLabel
+ OpBranch %23
+ %32 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %52 = OpIAdd %6 %96 %51
+ OpStore %19 %52
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %54 = OpIAdd %6 %91 %51
+ OpStore %8 %54
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %55 %9
+ OpBranch %56
+ %56 = OpLabel
+ %92 = OpPhi %6 %9 %12 %90 %59
+ OpLoopMerge %58 %59 None
+ OpBranch %60
+ %60 = OpLabel
+ %62 = OpSLessThan %17 %92 %16
+ OpBranchConditional %62 %57 %58
+ %57 = OpLabel
+ OpStore %63 %9
+ OpBranch %64
+ %64 = OpLabel
+ %93 = OpPhi %6 %9 %57 %88 %67
+ OpLoopMerge %66 %67 None
+ OpBranch %68
+ %68 = OpLabel
+ %70 = OpSLessThan %17 %93 %16
+ OpBranchConditional %70 %65 %66
+ %65 = OpLabel
+ %72 = OpSMod %6 %93 %28
+ %73 = OpIEqual %17 %72 %9
+ OpSelectionMerge %75 None
+ OpBranchConditional %73 %74 %66
+ %74 = OpLabel
+ %81 = OpAccessChain %7 %38 %92 %93
+ %82 = OpLoad %6 %81
+ %83 = OpIAdd %6 %82 %16
+ %84 = OpAccessChain %7 %76 %92 %93
+ OpStore %84 %83
+ OpBranch %75
+ %75 = OpLabel
+ OpBranch %67
+ %67 = OpLabel
+ %88 = OpIAdd %6 %93 %51
+ OpStore %63 %88
+ OpBranch %64
+ %66 = OpLabel
+ OpBranch %59
+ %59 = OpLabel
+ %90 = OpIAdd %6 %92 %51
+ OpStore %55 %90
+ OpBranch %56
+ %58 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 4u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[2]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[1], loops[2]);
+ EXPECT_FALSE(fusion.AreCompatible());
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK: [[PHI_2:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// j loop preheader removed manually
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int i = 0;
+ int j = 0;
+ // No loop-carried dependences, legal
+ for (; i < 10; i++) {
+ a[i] = a[i]*2;
+ }
+ for (; j < 10; j++) {
+ b[j] = a[j]+2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, DifferentArraysInLoopsNoPreheader) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %10 "j"
+ OpName %24 "a"
+ OpName %42 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 10
+ %18 = OpTypeBool
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 10
+ %22 = OpTypeArray %6 %21
+ %23 = OpTypePointer Function %22
+ %29 = OpConstant %6 2
+ %33 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %24 = OpVariable %23 Function
+ %42 = OpVariable %23 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpBranch %11
+ %11 = OpLabel
+ %51 = OpPhi %6 %9 %5 %34 %14
+ OpLoopMerge %35 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %18 %51 %17
+ OpBranchConditional %19 %12 %35
+ %12 = OpLabel
+ %27 = OpAccessChain %7 %24 %51
+ %28 = OpLoad %6 %27
+ %30 = OpIMul %6 %28 %29
+ %31 = OpAccessChain %7 %24 %51
+ OpStore %31 %30
+ OpBranch %14
+ %14 = OpLabel
+ %34 = OpIAdd %6 %51 %33
+ OpStore %8 %34
+ OpBranch %11
+ %35 = OpLabel
+ %52 = OpPhi %6 %9 %15 %50 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %18 %52 %17
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %24 %52
+ %46 = OpLoad %6 %45
+ %47 = OpIAdd %6 %46 %29
+ %48 = OpAccessChain %7 %42 %52
+ OpStore %48 %47
+ OpBranch %38
+ %38 = OpLabel
+ %50 = OpIAdd %6 %52 %33
+ OpStore %10 %50
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ {
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ ld.CreatePreHeaderBlocksIfMissing();
+
+ {
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+// j & k loop preheaders removed manually
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int i = 0;
+ int j = 0;
+ int k = 0;
+ // No loop-carried dependences, legal
+ for (; i < 10; i++) {
+ a[i] = a[i]*2;
+ }
+ for (; j < 10; j++) {
+ b[j] = a[j]+2;
+ }
+ for (; k < 10; k++) {
+ a[k] = a[k]*2;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, AdjacentLoopsNoPreheaders) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %10 "j"
+ OpName %11 "k"
+ OpName %25 "a"
+ OpName %43 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %18 = OpConstant %6 10
+ %19 = OpTypeBool
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 10
+ %23 = OpTypeArray %6 %22
+ %24 = OpTypePointer Function %23
+ %30 = OpConstant %6 2
+ %34 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %11 = OpVariable %7 Function
+ %25 = OpVariable %24 Function
+ %43 = OpVariable %24 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpStore %11 %9
+ OpBranch %12
+ %12 = OpLabel
+ %67 = OpPhi %6 %9 %5 %35 %15
+ OpLoopMerge %36 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %19 %67 %18
+ OpBranchConditional %20 %13 %36
+ %13 = OpLabel
+ %28 = OpAccessChain %7 %25 %67
+ %29 = OpLoad %6 %28
+ %31 = OpIMul %6 %29 %30
+ %32 = OpAccessChain %7 %25 %67
+ OpStore %32 %31
+ OpBranch %15
+ %15 = OpLabel
+ %35 = OpIAdd %6 %67 %34
+ OpStore %8 %35
+ OpBranch %12
+ %36 = OpLabel
+ %68 = OpPhi %6 %9 %16 %51 %39
+ OpLoopMerge %52 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %42 = OpSLessThan %19 %68 %18
+ OpBranchConditional %42 %37 %52
+ %37 = OpLabel
+ %46 = OpAccessChain %7 %25 %68
+ %47 = OpLoad %6 %46
+ %48 = OpIAdd %6 %47 %30
+ %49 = OpAccessChain %7 %43 %68
+ OpStore %49 %48
+ OpBranch %39
+ %39 = OpLabel
+ %51 = OpIAdd %6 %68 %34
+ OpStore %10 %51
+ OpBranch %36
+ %52 = OpLabel
+ %70 = OpPhi %6 %9 %40 %66 %55
+ OpLoopMerge %54 %55 None
+ OpBranch %56
+ %56 = OpLabel
+ %58 = OpSLessThan %19 %70 %18
+ OpBranchConditional %58 %53 %54
+ %53 = OpLabel
+ %61 = OpAccessChain %7 %25 %70
+ %62 = OpLoad %6 %61
+ %63 = OpIMul %6 %62 %30
+ %64 = OpAccessChain %7 %25 %70
+ OpStore %64 %63
+ OpBranch %55
+ %55 = OpLabel
+ %66 = OpIAdd %6 %70 %34
+ OpStore %11 %66
+ OpBranch %52
+ %54 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ {
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_FALSE(fusion.AreCompatible());
+ }
+
+ ld.CreatePreHeaderBlocksIfMissing();
+
+ {
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ std::string checks = R"(
+CHECK: [[PHI_0:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]]
+CHECK-NEXT: OpStore [[STORE_1]]
+CHECK: [[PHI_1:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]]
+CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]]
+CHECK-NEXT: OpStore [[STORE_2]]
+ )";
+
+ Match(checks, context.get());
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]]
+CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]]
+CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_2]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ int sum_0 = 0;
+ int sum_1 = 0;
+
+ // No loop-carried dependences, legal
+ for (int i = 0; i < 10; i++) {
+ sum_0 += a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ sum_1 += b[j];
+ }
+
+ int total = sum_0 + sum_1;
+}
+
+*/
+TEST_F(FusionLegalTest, IndependentReductions) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "sum_0"
+ OpName %10 "sum_1"
+ OpName %11 "i"
+ OpName %25 "a"
+ OpName %34 "j"
+ OpName %42 "b"
+ OpName %50 "total"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %18 = OpConstant %6 10
+ %19 = OpTypeBool
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 10
+ %23 = OpTypeArray %6 %22
+ %24 = OpTypePointer Function %23
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %11 = OpVariable %7 Function
+ %25 = OpVariable %24 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %24 Function
+ %50 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpStore %11 %9
+ OpBranch %12
+ %12 = OpLabel
+ %57 = OpPhi %6 %9 %5 %30 %15
+ %54 = OpPhi %6 %9 %5 %33 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %19 %54 %18
+ OpBranchConditional %20 %13 %14
+ %13 = OpLabel
+ %27 = OpAccessChain %7 %25 %54
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %57 %28
+ OpStore %8 %30
+ OpBranch %15
+ %15 = OpLabel
+ %33 = OpIAdd %6 %54 %32
+ OpStore %11 %33
+ OpBranch %12
+ %14 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %58 = OpPhi %6 %9 %14 %47 %38
+ %55 = OpPhi %6 %9 %14 %49 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %19 %55 %18
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %44 = OpAccessChain %7 %42 %55
+ %45 = OpLoad %6 %44
+ %47 = OpIAdd %6 %58 %45
+ OpStore %10 %47
+ OpBranch %38
+ %38 = OpLabel
+ %49 = OpIAdd %6 %55 %32
+ OpStore %34 %49
+ OpBranch %35
+ %37 = OpLabel
+ %53 = OpIAdd %6 %57 %58
+ OpStore %50 %53
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: [[SUM_0:%\w+]] = OpPhi
+CHECK-NEXT: [[SUM_1:%\w+]] = OpPhi
+CHECK-NEXT: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]]
+CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]]
+CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]]
+CHECK-NEXT: [[ADD_RES_1:%\w+]] = OpIAdd {{%\w+}} [[SUM_1]] [[LOAD_RES_1]]
+CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ int sum_0 = 0;
+ int sum_1 = 0;
+
+ // No loop-carried dependences, legal
+ for (int i = 0; i < 10; i++) {
+ sum_0 += a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ sum_1 += b[j];
+ }
+
+ int total = sum_0 + sum_1;
+}
+
+*/
+TEST_F(FusionLegalTest, IndependentReductionsOneLCSSA) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "sum_0"
+ OpName %10 "sum_1"
+ OpName %11 "i"
+ OpName %25 "a"
+ OpName %34 "j"
+ OpName %42 "b"
+ OpName %50 "total"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %18 = OpConstant %6 10
+ %19 = OpTypeBool
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 10
+ %23 = OpTypeArray %6 %22
+ %24 = OpTypePointer Function %23
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %11 = OpVariable %7 Function
+ %25 = OpVariable %24 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %24 Function
+ %50 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpStore %11 %9
+ OpBranch %12
+ %12 = OpLabel
+ %57 = OpPhi %6 %9 %5 %30 %15
+ %54 = OpPhi %6 %9 %5 %33 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %19 %54 %18
+ OpBranchConditional %20 %13 %14
+ %13 = OpLabel
+ %27 = OpAccessChain %7 %25 %54
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %57 %28
+ OpStore %8 %30
+ OpBranch %15
+ %15 = OpLabel
+ %33 = OpIAdd %6 %54 %32
+ OpStore %11 %33
+ OpBranch %12
+ %14 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %58 = OpPhi %6 %9 %14 %47 %38
+ %55 = OpPhi %6 %9 %14 %49 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %19 %55 %18
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %44 = OpAccessChain %7 %42 %55
+ %45 = OpLoad %6 %44
+ %47 = OpIAdd %6 %58 %45
+ OpStore %10 %47
+ OpBranch %38
+ %38 = OpLabel
+ %49 = OpIAdd %6 %55 %32
+ OpStore %34 %49
+ OpBranch %35
+ %37 = OpLabel
+ %53 = OpIAdd %6 %57 %58
+ OpStore %50 %53
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopUtils utils_0(context.get(), loops[0]);
+ utils_0.MakeLoopClosedSSA();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: [[SUM_0:%\w+]] = OpPhi
+CHECK-NEXT: [[SUM_1:%\w+]] = OpPhi
+CHECK-NEXT: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]]
+CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]]
+CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]]
+CHECK-NEXT: [[ADD_RES_1:%\w+]] = OpIAdd {{%\w+}} [[SUM_1]] [[LOAD_RES_1]]
+CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ int sum_0 = 0;
+ int sum_1 = 0;
+
+ // No loop-carried dependences, legal
+ for (int i = 0; i < 10; i++) {
+ sum_0 += a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ sum_1 += b[j];
+ }
+
+ int total = sum_0 + sum_1;
+}
+
+*/
+TEST_F(FusionLegalTest, IndependentReductionsBothLCSSA) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "sum_0"
+ OpName %10 "sum_1"
+ OpName %11 "i"
+ OpName %25 "a"
+ OpName %34 "j"
+ OpName %42 "b"
+ OpName %50 "total"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %18 = OpConstant %6 10
+ %19 = OpTypeBool
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 10
+ %23 = OpTypeArray %6 %22
+ %24 = OpTypePointer Function %23
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %11 = OpVariable %7 Function
+ %25 = OpVariable %24 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %24 Function
+ %50 = OpVariable %7 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpStore %11 %9
+ OpBranch %12
+ %12 = OpLabel
+ %57 = OpPhi %6 %9 %5 %30 %15
+ %54 = OpPhi %6 %9 %5 %33 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %19 %54 %18
+ OpBranchConditional %20 %13 %14
+ %13 = OpLabel
+ %27 = OpAccessChain %7 %25 %54
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %57 %28
+ OpStore %8 %30
+ OpBranch %15
+ %15 = OpLabel
+ %33 = OpIAdd %6 %54 %32
+ OpStore %11 %33
+ OpBranch %12
+ %14 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %58 = OpPhi %6 %9 %14 %47 %38
+ %55 = OpPhi %6 %9 %14 %49 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %19 %55 %18
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %44 = OpAccessChain %7 %42 %55
+ %45 = OpLoad %6 %44
+ %47 = OpIAdd %6 %58 %45
+ OpStore %10 %47
+ OpBranch %38
+ %38 = OpLabel
+ %49 = OpIAdd %6 %55 %32
+ OpStore %34 %49
+ OpBranch %35
+ %37 = OpLabel
+ %53 = OpIAdd %6 %57 %58
+ OpStore %50 %53
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopUtils utils_0(context.get(), loops[0]);
+ utils_0.MakeLoopClosedSSA();
+ LoopUtils utils_1(context.get(), loops[1]);
+ utils_1.MakeLoopClosedSSA();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: [[SUM_0:%\w+]] = OpPhi
+CHECK-NEXT: [[SUM_1:%\w+]] = OpPhi
+CHECK-NEXT: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]]
+CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]]
+CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]]
+CHECK-NEXT: [[ADD_RES_1:%\w+]] = OpIAdd {{%\w+}} [[SUM_1]] [[LOAD_RES_1]]
+CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+
+ int sum_0 = 0;
+
+ // No loop-carried dependences, legal
+ for (int i = 0; i < 10; i++) {
+ sum_0 += a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ a[j] = b[j];
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, LoadStoreReductionAndNonLoopCarriedDependence) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "sum_0"
+ OpName %10 "i"
+ OpName %24 "a"
+ OpName %33 "j"
+ OpName %42 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 10
+ %18 = OpTypeBool
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 10
+ %22 = OpTypeArray %6 %21
+ %23 = OpTypePointer Function %22
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %10 = OpVariable %7 Function
+ %24 = OpVariable %23 Function
+ %33 = OpVariable %7 Function
+ %42 = OpVariable %23 Function
+ OpStore %8 %9
+ OpStore %10 %9
+ OpBranch %11
+ %11 = OpLabel
+ %51 = OpPhi %6 %9 %5 %29 %14
+ %49 = OpPhi %6 %9 %5 %32 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %18 %49 %17
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ %26 = OpAccessChain %7 %24 %49
+ %27 = OpLoad %6 %26
+ %29 = OpIAdd %6 %51 %27
+ OpStore %8 %29
+ OpBranch %14
+ %14 = OpLabel
+ %32 = OpIAdd %6 %49 %31
+ OpStore %10 %32
+ OpBranch %11
+ %13 = OpLabel
+ OpStore %33 %9
+ OpBranch %34
+ %34 = OpLabel
+ %50 = OpPhi %6 %9 %13 %48 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %40 = OpSLessThan %18 %50 %17
+ OpBranchConditional %40 %35 %36
+ %35 = OpLabel
+ %44 = OpAccessChain %7 %42 %50
+ %45 = OpLoad %6 %44
+ %46 = OpAccessChain %7 %24 %50
+ OpStore %46 %45
+ OpBranch %37
+ %37 = OpLabel
+ %48 = OpIAdd %6 %50 %31
+ OpStore %33 %48
+ OpBranch %34
+ %36 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ // TODO: Loop descriptor doesn't return induction variables but all OpPhi
+ // in the header and LoopDependenceAnalysis falls over.
+ // EXPECT_TRUE(fusion.IsLegal());
+
+ // fusion.Fuse();
+ }
+
+ {
+ // LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ // EXPECT_EQ(ld.NumLoops(), 1u);
+
+ // std::string checks = R"(
+ // CHECK: [[SUM_0:%\w+]] = OpPhi
+ // CHECK-NEXT: [[PHI:%\w+]] = OpPhi
+ // CHECK-NEXT: OpLoopMerge
+ // CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+ // CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]]
+ // CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]]
+ // CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]]
+ // CHECK-NOT: OpPhi
+ // CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+ // CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]]
+ // CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+ // CHECK-NEXT: OpStore [[STORE_1]] [[LOAD_RES_1]]
+ // )";
+
+ // Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+int x;
+void main() {
+ int[10] a;
+ int[10] b;
+
+ // Legal.
+ for (int i = 0; i < 10; i++) {
+ x += a[i];
+ }
+ for (int j = 0; j < 10; j++) {
+ b[j] = b[j]+1;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, ReductionAndNonLoopCarriedDependence) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %20 "x"
+ OpName %25 "a"
+ OpName %34 "j"
+ OpName %42 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypePointer Private %6
+ %20 = OpVariable %19 Private
+ %21 = OpTypeInt 32 0
+ %22 = OpConstant %21 10
+ %23 = OpTypeArray %6 %22
+ %24 = OpTypePointer Function %23
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %25 = OpVariable %24 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %24 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %51
+ %28 = OpLoad %6 %27
+ %29 = OpLoad %6 %20
+ %30 = OpIAdd %6 %29 %28
+ OpStore %20 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %51 %32
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %52 = OpPhi %6 %9 %12 %50 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %52 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %42 %52
+ %46 = OpLoad %6 %45
+ %47 = OpIAdd %6 %46 %32
+ %48 = OpAccessChain %7 %42 %52
+ OpStore %48 %47
+ OpBranch %38
+ %38 = OpLabel
+ %50 = OpIAdd %6 %52 %32
+ OpStore %34 %50
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ std::string checks = R"(
+CHECK: OpName [[X:%\w+]] "x"
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]]
+CHECK-NEXT: [[X_LOAD:%\w+]] = OpLoad {{%\w+}} [[X]]
+CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[X_LOAD]] [[LOAD_RES_0]]
+CHECK-NEXT: OpStore [[X]] [[ADD_RES_0]]
+CHECK-NOT: OpPhi
+CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: {{%\w+}} = OpLoad {{%\w+}} [[LOAD_1]]
+CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]]
+CHECK-NEXT: OpStore [[STORE_1]]
+ )";
+
+ Match(checks, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+struct TestStruct {
+ int[10] a;
+ int b;
+};
+
+void main() {
+ TestStruct test_0;
+ TestStruct test_1;
+ TestStruct test_2;
+
+ test_1.b = 2;
+
+ for (int i = 0; i < 10; i++) {
+ test_0.a[i] = i;
+ }
+ for (int j = 0; j < 10; j++) {
+ test_2 = test_1;
+ }
+}
+
+*/
+TEST_F(FusionLegalTest, ArrayInStruct) {
+ std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %10 "TestStruct"
+ OpMemberName %10 0 "a"
+ OpMemberName %10 1 "b"
+ OpName %12 "test_1"
+ OpName %17 "i"
+ OpName %28 "test_0"
+ OpName %34 "j"
+ OpName %42 "test_2"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeInt 32 0
+ %8 = OpConstant %7 10
+ %9 = OpTypeArray %6 %8
+ %10 = OpTypeStruct %9 %6
+ %11 = OpTypePointer Function %10
+ %13 = OpConstant %6 1
+ %14 = OpConstant %6 2
+ %15 = OpTypePointer Function %6
+ %18 = OpConstant %6 0
+ %25 = OpConstant %6 10
+ %26 = OpTypeBool
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %12 = OpVariable %11 Function
+ %17 = OpVariable %15 Function
+ %28 = OpVariable %11 Function
+ %34 = OpVariable %15 Function
+ %42 = OpVariable %11 Function
+ %16 = OpAccessChain %15 %12 %13
+ OpStore %16 %14
+ OpStore %17 %18
+ OpBranch %19
+ %19 = OpLabel
+ %46 = OpPhi %6 %18 %5 %33 %22
+ OpLoopMerge %21 %22 None
+ OpBranch %23
+ %23 = OpLabel
+ %27 = OpSLessThan %26 %46 %25
+ OpBranchConditional %27 %20 %21
+ %20 = OpLabel
+ %31 = OpAccessChain %15 %28 %18 %46
+ OpStore %31 %46
+ OpBranch %22
+ %22 = OpLabel
+ %33 = OpIAdd %6 %46 %13
+ OpStore %17 %33
+ OpBranch %19
+ %21 = OpLabel
+ OpStore %34 %18
+ OpBranch %35
+ %35 = OpLabel
+ %47 = OpPhi %6 %18 %21 %45 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %26 %47 %25
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %43 = OpLoad %10 %12
+ OpStore %42 %43
+ OpBranch %38
+ %38 = OpLabel
+ %45 = OpIAdd %6 %47 %13
+ OpStore %34 %45
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ auto loops = ld.GetLoopsInBinaryLayoutOrder();
+
+ LoopFusion fusion(context.get(), loops[0], loops[1]);
+ EXPECT_TRUE(fusion.AreCompatible());
+ EXPECT_TRUE(fusion.IsLegal());
+
+ fusion.Fuse();
+ }
+
+ {
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ // clang-format off
+ std::string checks = R"(
+CHECK: OpName [[TEST_1:%\w+]] "test_1"
+CHECK: OpName [[TEST_0:%\w+]] "test_0"
+CHECK: OpName [[TEST_2:%\w+]] "test_2"
+CHECK: [[PHI:%\w+]] = OpPhi
+CHECK-NEXT: OpLoopMerge
+CHECK: [[TEST_0_STORE:%\w+]] = OpAccessChain {{%\w+}} [[TEST_0]] {{%\w+}} {{%\w+}}
+CHECK-NEXT: OpStore [[TEST_0_STORE]] [[PHI]]
+CHECK-NOT: OpPhi
+CHECK: [[TEST_1_LOAD:%\w+]] = OpLoad {{%\w+}} [[TEST_1]]
+CHECK: OpStore [[TEST_2]] [[TEST_1_LOAD]]
+ )";
+ // clang-format on
+
+ Match(checks, context.get());
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/fusion_pass.cpp b/test/opt/loop_optimizations/fusion_pass.cpp
new file mode 100644
index 0000000..9493923
--- /dev/null
+++ b/test/opt/loop_optimizations/fusion_pass.cpp
@@ -0,0 +1,717 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using FusionPassTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ for (int i = 0; i < 10; i++) {
+ a[i] = a[i]*2;
+ }
+ for (int i = 0; i < 10; i++) {
+ b[i] = a[i]+2;
+ }
+}
+
+*/
+TEST_F(FusionPassTest, SimpleFusion) {
+ const std::string text = R"(
+; CHECK: OpPhi
+; CHECK: OpLoad
+; CHECK: OpStore
+; CHECK-NOT: OpPhi
+; CHECK: OpLoad
+; CHECK: OpStore
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %34 "i"
+ OpName %42 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %28 = OpConstant %6 2
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpAccessChain %7 %23 %51
+ %27 = OpLoad %6 %26
+ %29 = OpIMul %6 %27 %28
+ %30 = OpAccessChain %7 %23 %51
+ OpStore %30 %29
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %51 %32
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %52 = OpPhi %6 %9 %12 %50 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %52 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %23 %52
+ %46 = OpLoad %6 %45
+ %47 = OpIAdd %6 %46 %28
+ %48 = OpAccessChain %7 %42 %52
+ OpStore %48 %47
+ OpBranch %38
+ %38 = OpLabel
+ %50 = OpIAdd %6 %52 %32
+ OpStore %34 %50
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopFusionPass>(text, true, 20);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i] + 2;
+ }
+ for (int i = 0; i < 10; i++) {
+ b[i] = c[i] + 10;
+ }
+}
+
+*/
+TEST_F(FusionPassTest, ThreeLoopsFused) {
+ const std::string text = R"(
+; CHECK: OpPhi
+; CHECK: OpLoad
+; CHECK: OpStore
+; CHECK-NOT: OpPhi
+; CHECK: OpLoad
+; CHECK: OpStore
+; CHECK-NOT: OpPhi
+; CHECK: OpLoad
+; CHECK: OpStore
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %34 "i"
+ OpName %42 "c"
+ OpName %52 "i"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %47 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ %52 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %68 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %68 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %68
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %68
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %68 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %69 = OpPhi %6 %9 %12 %51 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %69 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %23 %69
+ %46 = OpLoad %6 %45
+ %48 = OpIAdd %6 %46 %47
+ %49 = OpAccessChain %7 %42 %69
+ OpStore %49 %48
+ OpBranch %38
+ %38 = OpLabel
+ %51 = OpIAdd %6 %69 %29
+ OpStore %34 %51
+ OpBranch %35
+ %37 = OpLabel
+ OpStore %52 %9
+ OpBranch %53
+ %53 = OpLabel
+ %70 = OpPhi %6 %9 %37 %67 %56
+ OpLoopMerge %55 %56 None
+ OpBranch %57
+ %57 = OpLabel
+ %59 = OpSLessThan %17 %70 %16
+ OpBranchConditional %59 %54 %55
+ %54 = OpLabel
+ %62 = OpAccessChain %7 %42 %70
+ %63 = OpLoad %6 %62
+ %64 = OpIAdd %6 %63 %16
+ %65 = OpAccessChain %7 %25 %70
+ OpStore %65 %64
+ OpBranch %56
+ %56 = OpLabel
+ %67 = OpIAdd %6 %70 %29
+ OpStore %52 %67
+ OpBranch %53
+ %55 = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ )";
+
+ SinglePassRunAndMatch<LoopFusionPass>(text, true, 20);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10][10] a;
+ int[10][10] b;
+ int[10][10] c;
+ // Legal both
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ c[i][j] = a[i][j] + 2;
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ b[i][j] = c[i][j] + 10;
+ }
+ }
+}
+
+*/
+TEST_F(FusionPassTest, NestedLoopsFused) {
+ const std::string text = R"(
+; CHECK: OpPhi
+; CHECK: OpPhi
+; CHECK: OpLoad
+; CHECK: OpStore
+; CHECK-NOT: OpPhi
+; CHECK: OpLoad
+; CHECK: OpStore
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %19 "j"
+ OpName %32 "c"
+ OpName %35 "a"
+ OpName %48 "i"
+ OpName %56 "j"
+ OpName %64 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %27 = OpTypeInt 32 0
+ %28 = OpConstant %27 10
+ %29 = OpTypeArray %6 %28
+ %30 = OpTypeArray %29 %28
+ %31 = OpTypePointer Function %30
+ %40 = OpConstant %6 2
+ %44 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %32 = OpVariable %31 Function
+ %35 = OpVariable %31 Function
+ %48 = OpVariable %7 Function
+ %56 = OpVariable %7 Function
+ %64 = OpVariable %31 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %77 = OpPhi %6 %9 %5 %47 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %77 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpStore %19 %9
+ OpBranch %20
+ %20 = OpLabel
+ %81 = OpPhi %6 %9 %11 %45 %23
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpSLessThan %17 %81 %16
+ OpBranchConditional %26 %21 %22
+ %21 = OpLabel
+ %38 = OpAccessChain %7 %35 %77 %81
+ %39 = OpLoad %6 %38
+ %41 = OpIAdd %6 %39 %40
+ %42 = OpAccessChain %7 %32 %77 %81
+ OpStore %42 %41
+ OpBranch %23
+ %23 = OpLabel
+ %45 = OpIAdd %6 %81 %44
+ OpStore %19 %45
+ OpBranch %20
+ %22 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %47 = OpIAdd %6 %77 %44
+ OpStore %8 %47
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %48 %9
+ OpBranch %49
+ %49 = OpLabel
+ %78 = OpPhi %6 %9 %12 %76 %52
+ OpLoopMerge %51 %52 None
+ OpBranch %53
+ %53 = OpLabel
+ %55 = OpSLessThan %17 %78 %16
+ OpBranchConditional %55 %50 %51
+ %50 = OpLabel
+ OpStore %56 %9
+ OpBranch %57
+ %57 = OpLabel
+ %79 = OpPhi %6 %9 %50 %74 %60
+ OpLoopMerge %59 %60 None
+ OpBranch %61
+ %61 = OpLabel
+ %63 = OpSLessThan %17 %79 %16
+ OpBranchConditional %63 %58 %59
+ %58 = OpLabel
+ %69 = OpAccessChain %7 %32 %78 %79
+ %70 = OpLoad %6 %69
+ %71 = OpIAdd %6 %70 %16
+ %72 = OpAccessChain %7 %64 %78 %79
+ OpStore %72 %71
+ OpBranch %60
+ %60 = OpLabel
+ %74 = OpIAdd %6 %79 %44
+ OpStore %56 %74
+ OpBranch %57
+ %59 = OpLabel
+ OpBranch %52
+ %52 = OpLabel
+ %76 = OpIAdd %6 %78 %44
+ OpStore %48 %76
+ OpBranch %49
+ %51 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopFusionPass>(text, true, 20);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ // Can't fuse, different step
+ for (int i = 0; i < 10; i++) {}
+ for (int j = 0; j < 10; j=j+2) {}
+}
+
+*/
+TEST_F(FusionPassTest, Incompatible) {
+ const std::string text = R"(
+; CHECK: OpPhi
+; CHECK-NEXT: OpLoopMerge
+; CHECK: OpPhi
+; CHECK-NEXT: OpLoopMerge
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %22 "j"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %20 = OpConstant %6 1
+ %31 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %22 = OpVariable %7 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %33 = OpPhi %6 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %33 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %6 %33 %20
+ OpStore %8 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %22 %9
+ OpBranch %23
+ %23 = OpLabel
+ %34 = OpPhi %6 %9 %12 %32 %26
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %17 %34 %16
+ OpBranchConditional %29 %24 %25
+ %24 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %32 = OpIAdd %6 %34 %31
+ OpStore %22 %32
+ OpBranch %23
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopFusionPass>(text, true, 20);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ int[10] c;
+ // Illegal, loop-independent dependence will become a
+ // backward loop-carried antidependence
+ for (int i = 0; i < 10; i++) {
+ a[i] = b[i] + 1;
+ }
+ for (int i = 0; i < 10; i++) {
+ c[i] = a[i+1] + 2;
+ }
+}
+
+*/
+TEST_F(FusionPassTest, Illegal) {
+ std::string text = R"(
+; CHECK: OpPhi
+; CHECK-NEXT: OpLoopMerge
+; CHECK: OpLoad
+; CHECK: OpStore
+; CHECK: OpPhi
+; CHECK-NEXT: OpLoopMerge
+; CHECK: OpLoad
+; CHECK: OpStore
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %25 "b"
+ OpName %34 "i"
+ OpName %42 "c"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %29 = OpConstant %6 1
+ %48 = OpConstant %6 2
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %25 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %53 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %53 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpAccessChain %7 %25 %53
+ %28 = OpLoad %6 %27
+ %30 = OpIAdd %6 %28 %29
+ %31 = OpAccessChain %7 %23 %53
+ OpStore %31 %30
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %53 %29
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %54 = OpPhi %6 %9 %12 %52 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %54 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpIAdd %6 %54 %29
+ %46 = OpAccessChain %7 %23 %45
+ %47 = OpLoad %6 %46
+ %49 = OpIAdd %6 %47 %48
+ %50 = OpAccessChain %7 %42 %54
+ OpStore %50 %49
+ OpBranch %38
+ %38 = OpLabel
+ %52 = OpIAdd %6 %54 %29
+ OpStore %34 %52
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopFusionPass>(text, true, 20);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+void main() {
+ int[10] a;
+ int[10] b;
+ for (int i = 0; i < 10; i++) {
+ a[i] = a[i]*2;
+ }
+ for (int i = 0; i < 10; i++) {
+ b[i] = a[i]+2;
+ }
+}
+
+*/
+TEST_F(FusionPassTest, TooManyRegisters) {
+ const std::string text = R"(
+; CHECK: OpPhi
+; CHECK-NEXT: OpLoopMerge
+; CHECK: OpLoad
+; CHECK: OpStore
+; CHECK: OpPhi
+; CHECK-NEXT: OpLoopMerge
+; CHECK: OpLoad
+; CHECK: OpStore
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ OpName %8 "i"
+ OpName %23 "a"
+ OpName %34 "i"
+ OpName %42 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 10
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %28 = OpConstant %6 2
+ %32 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %23 = OpVariable %22 Function
+ %34 = OpVariable %7 Function
+ %42 = OpVariable %22 Function
+ OpStore %8 %9
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %33 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpAccessChain %7 %23 %51
+ %27 = OpLoad %6 %26
+ %29 = OpIMul %6 %27 %28
+ %30 = OpAccessChain %7 %23 %51
+ OpStore %30 %29
+ OpBranch %13
+ %13 = OpLabel
+ %33 = OpIAdd %6 %51 %32
+ OpStore %8 %33
+ OpBranch %10
+ %12 = OpLabel
+ OpStore %34 %9
+ OpBranch %35
+ %35 = OpLabel
+ %52 = OpPhi %6 %9 %12 %50 %38
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %41 = OpSLessThan %17 %52 %16
+ OpBranchConditional %41 %36 %37
+ %36 = OpLabel
+ %45 = OpAccessChain %7 %23 %52
+ %46 = OpLoad %6 %45
+ %47 = OpIAdd %6 %46 %28
+ %48 = OpAccessChain %7 %42 %52
+ OpStore %48 %47
+ OpBranch %38
+ %38 = OpLabel
+ %50 = OpIAdd %6 %52 %32
+ OpStore %34 %50
+ OpBranch %35
+ %37 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopFusionPass>(text, true, 5);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_all_loop_types.cpp b/test/opt/loop_optimizations/hoist_all_loop_types.cpp
new file mode 100644
index 0000000..27e0a0d
--- /dev/null
+++ b/test/opt/loop_optimizations/hoist_all_loop_types.cpp
@@ -0,0 +1,285 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "source/opt/licm_pass.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Tests that all loop types are handled appropriately by the LICM pass.
+
+ Generated from the following GLSL fragment shader
+--eliminate-local-multi-store has also been run on the spv binary
+#version 440 core
+void main(){
+ int i_1 = 0;
+ for (i_1 = 0; i_1 < 10; i_1++) {
+ }
+ int i_2 = 0;
+ while (i_2 < 10) {
+ i_2++;
+ }
+ int i_3 = 0;
+ do {
+ i_3++;
+ } while (i_3 < 10);
+ int hoist = 0;
+ int i_4 = 0;
+ int i_5 = 0;
+ int i_6 = 0;
+ for (i_4 = 0; i_4 < 10; i_4++) {
+ while (i_5 < 10) {
+ do {
+ hoist = i_1 + i_2 + i_3;
+ i_6++;
+ } while (i_6 < 10);
+ i_5++;
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, AllLoopTypes) {
+ const std::string before_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %4
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %int %int_0 %11 %14 %15
+OpLoopMerge %16 %15 None
+OpBranch %17
+%17 = OpLabel
+%18 = OpSLessThan %bool %13 %int_10
+OpBranchConditional %18 %19 %16
+%19 = OpLabel
+OpBranch %15
+%15 = OpLabel
+%14 = OpIAdd %int %13 %int_1
+OpBranch %12
+%16 = OpLabel
+OpBranch %20
+%20 = OpLabel
+%21 = OpPhi %int %int_0 %16 %22 %23
+OpLoopMerge %24 %23 None
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%22 = OpIAdd %int %21 %int_1
+OpBranch %23
+%23 = OpLabel
+OpBranch %20
+%24 = OpLabel
+OpBranch %28
+%28 = OpLabel
+%29 = OpPhi %int %int_0 %24 %30 %31
+OpLoopMerge %32 %31 None
+OpBranch %33
+%33 = OpLabel
+%30 = OpIAdd %int %29 %int_1
+OpBranch %31
+%31 = OpLabel
+%34 = OpSLessThan %bool %30 %int_10
+OpBranchConditional %34 %28 %32
+%32 = OpLabel
+OpBranch %35
+%35 = OpLabel
+%36 = OpPhi %int %int_0 %32 %37 %38
+%39 = OpPhi %int %int_0 %32 %40 %38
+%41 = OpPhi %int %int_0 %32 %42 %38
+%43 = OpPhi %int %int_0 %32 %44 %38
+OpLoopMerge %45 %38 None
+OpBranch %46
+%46 = OpLabel
+%47 = OpSLessThan %bool %39 %int_10
+OpBranchConditional %47 %48 %45
+%48 = OpLabel
+OpBranch %49
+%49 = OpLabel
+%37 = OpPhi %int %36 %48 %50 %51
+%42 = OpPhi %int %41 %48 %52 %51
+%44 = OpPhi %int %43 %48 %53 %51
+OpLoopMerge %54 %51 None
+OpBranch %55
+%55 = OpLabel
+%56 = OpSLessThan %bool %42 %int_10
+OpBranchConditional %56 %57 %54
+%57 = OpLabel
+OpBranch %58
+%58 = OpLabel
+%59 = OpPhi %int %37 %57 %50 %60
+%61 = OpPhi %int %44 %57 %53 %60
+OpLoopMerge %62 %60 None
+OpBranch %63
+%63 = OpLabel
+%64 = OpIAdd %int %13 %21
+%50 = OpIAdd %int %64 %30
+%53 = OpIAdd %int %61 %int_1
+OpBranch %60
+%60 = OpLabel
+%65 = OpSLessThan %bool %53 %int_10
+OpBranchConditional %65 %58 %62
+%62 = OpLabel
+%52 = OpIAdd %int %42 %int_1
+OpBranch %51
+%51 = OpLabel
+OpBranch %49
+%54 = OpLabel
+OpBranch %38
+%38 = OpLabel
+%40 = OpIAdd %int %39 %int_1
+OpBranch %35
+%45 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_1 = OpConstant %int 1
+%main = OpFunction %void None %4
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %int %int_0 %11 %14 %15
+OpLoopMerge %16 %15 None
+OpBranch %17
+%17 = OpLabel
+%18 = OpSLessThan %bool %13 %int_10
+OpBranchConditional %18 %19 %16
+%19 = OpLabel
+OpBranch %15
+%15 = OpLabel
+%14 = OpIAdd %int %13 %int_1
+OpBranch %12
+%16 = OpLabel
+OpBranch %20
+%20 = OpLabel
+%21 = OpPhi %int %int_0 %16 %22 %23
+OpLoopMerge %24 %23 None
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%22 = OpIAdd %int %21 %int_1
+OpBranch %23
+%23 = OpLabel
+OpBranch %20
+%24 = OpLabel
+OpBranch %28
+%28 = OpLabel
+%29 = OpPhi %int %int_0 %24 %30 %31
+OpLoopMerge %32 %31 None
+OpBranch %33
+%33 = OpLabel
+%30 = OpIAdd %int %29 %int_1
+OpBranch %31
+%31 = OpLabel
+%34 = OpSLessThan %bool %30 %int_10
+OpBranchConditional %34 %28 %32
+%32 = OpLabel
+%64 = OpIAdd %int %13 %21
+%50 = OpIAdd %int %64 %30
+OpBranch %35
+%35 = OpLabel
+%36 = OpPhi %int %int_0 %32 %37 %38
+%39 = OpPhi %int %int_0 %32 %40 %38
+%41 = OpPhi %int %int_0 %32 %42 %38
+%43 = OpPhi %int %int_0 %32 %44 %38
+OpLoopMerge %45 %38 None
+OpBranch %46
+%46 = OpLabel
+%47 = OpSLessThan %bool %39 %int_10
+OpBranchConditional %47 %48 %45
+%48 = OpLabel
+OpBranch %49
+%49 = OpLabel
+%37 = OpPhi %int %36 %48 %50 %51
+%42 = OpPhi %int %41 %48 %52 %51
+%44 = OpPhi %int %43 %48 %53 %51
+OpLoopMerge %54 %51 None
+OpBranch %55
+%55 = OpLabel
+%56 = OpSLessThan %bool %42 %int_10
+OpBranchConditional %56 %57 %54
+%57 = OpLabel
+OpBranch %58
+%58 = OpLabel
+%59 = OpPhi %int %37 %57 %50 %60
+%61 = OpPhi %int %44 %57 %53 %60
+OpLoopMerge %62 %60 None
+OpBranch %63
+%63 = OpLabel
+%53 = OpIAdd %int %61 %int_1
+OpBranch %60
+%60 = OpLabel
+%65 = OpSLessThan %bool %53 %int_10
+OpBranchConditional %65 %58 %62
+%62 = OpLabel
+%52 = OpIAdd %int %42 %int_1
+OpBranch %51
+%51 = OpLabel
+OpBranch %49
+%54 = OpLabel
+OpBranch %38
+%38 = OpLabel
+%40 = OpIAdd %int %39 %int_1
+OpBranch %35
+%45 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LICMPass>(before_hoist, after_hoist, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_double_nested_loops.cpp b/test/opt/loop_optimizations/hoist_double_nested_loops.cpp
new file mode 100644
index 0000000..ea19496
--- /dev/null
+++ b/test/opt/loop_optimizations/hoist_double_nested_loops.cpp
@@ -0,0 +1,162 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "source/opt/licm_pass.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Tests that the LICM pass will move invariants through multiple loops
+
+ Generated from the following GLSL fragment shader
+--eliminate-local-multi-store has also been run on the spv binary
+#version 440 core
+void main(){
+ int a = 2;
+ int b = 1;
+ int hoist = 0;
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ // hoist 'hoist = a - b' out of both loops
+ hoist = a - b;
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, NestedDoubleHoist) {
+ const std::string before_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_2 = OpConstant %int 2
+%int_1 = OpConstant %int 1
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%12 = OpUndef %int
+%main = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+%18 = OpPhi %int %int_0 %13 %19 %17
+%20 = OpPhi %int %12 %13 %21 %17
+OpLoopMerge %22 %17 None
+OpBranch %23
+%23 = OpLabel
+%24 = OpSLessThan %bool %18 %int_10
+OpBranchConditional %24 %25 %22
+%25 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%16 = OpPhi %int %15 %25 %27 %28
+%21 = OpPhi %int %int_0 %25 %29 %28
+OpLoopMerge %30 %28 None
+OpBranch %31
+%31 = OpLabel
+%32 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %32 %33 %30
+%33 = OpLabel
+%27 = OpISub %int %int_2 %int_1
+OpBranch %28
+%28 = OpLabel
+%29 = OpIAdd %int %21 %int_1
+OpBranch %26
+%30 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%19 = OpIAdd %int %18 %int_1
+OpBranch %14
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_2 = OpConstant %int 2
+%int_1 = OpConstant %int 1
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%12 = OpUndef %int
+%main = OpFunction %void None %4
+%13 = OpLabel
+%27 = OpISub %int %int_2 %int_1
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+%18 = OpPhi %int %int_0 %13 %19 %17
+%20 = OpPhi %int %12 %13 %21 %17
+OpLoopMerge %22 %17 None
+OpBranch %23
+%23 = OpLabel
+%24 = OpSLessThan %bool %18 %int_10
+OpBranchConditional %24 %25 %22
+%25 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%16 = OpPhi %int %15 %25 %27 %28
+%21 = OpPhi %int %int_0 %25 %29 %28
+OpLoopMerge %30 %28 None
+OpBranch %31
+%31 = OpLabel
+%32 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %32 %33 %30
+%33 = OpLabel
+OpBranch %28
+%28 = OpLabel
+%29 = OpIAdd %int %21 %int_1
+OpBranch %26
+%30 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%19 = OpIAdd %int %18 %int_1
+OpBranch %14
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LICMPass>(before_hoist, after_hoist, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_from_independent_loops.cpp b/test/opt/loop_optimizations/hoist_from_independent_loops.cpp
new file mode 100644
index 0000000..abc79e3
--- /dev/null
+++ b/test/opt/loop_optimizations/hoist_from_independent_loops.cpp
@@ -0,0 +1,201 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "source/opt/licm_pass.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Tests that the LICM pass will analyse multiple independent loops in a function
+
+ Generated from the following GLSL fragment shader
+--eliminate-local-multi-store has also been run on the spv binary
+#version 440 core
+void main(){
+ int a = 1;
+ int b = 2;
+ int hoist = 0;
+ for (int i = 0; i < 10; i++) {
+ // invariant
+ hoist = a + b;
+ }
+ for (int i = 0; i < 10; i++) {
+ // invariant
+ hoist = a + b;
+ }
+ int c = 1;
+ int d = 2;
+ int hoist2 = 0;
+ for (int i = 0; i < 10; i++) {
+ // invariant
+ hoist2 = c + d;
+ }
+}
+*/
+TEST_F(PassClassTest, HoistFromIndependentLoops) {
+ const std::string before_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%main = OpFunction %void None %4
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+%14 = OpPhi %int %int_0 %12 %15 %16
+%17 = OpPhi %int %int_0 %12 %18 %16
+OpLoopMerge %19 %16 None
+OpBranch %20
+%20 = OpLabel
+%21 = OpSLessThan %bool %17 %int_10
+OpBranchConditional %21 %22 %19
+%22 = OpLabel
+%15 = OpIAdd %int %int_1 %int_2
+OpBranch %16
+%16 = OpLabel
+%18 = OpIAdd %int %17 %int_1
+OpBranch %13
+%19 = OpLabel
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %int %14 %19 %25 %26
+%27 = OpPhi %int %int_0 %19 %28 %26
+OpLoopMerge %29 %26 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpSLessThan %bool %27 %int_10
+OpBranchConditional %31 %32 %29
+%32 = OpLabel
+%25 = OpIAdd %int %int_1 %int_2
+OpBranch %26
+%26 = OpLabel
+%28 = OpIAdd %int %27 %int_1
+OpBranch %23
+%29 = OpLabel
+OpBranch %33
+%33 = OpLabel
+%34 = OpPhi %int %int_0 %29 %35 %36
+%37 = OpPhi %int %int_0 %29 %38 %36
+OpLoopMerge %39 %36 None
+OpBranch %40
+%40 = OpLabel
+%41 = OpSLessThan %bool %37 %int_10
+OpBranchConditional %41 %42 %39
+%42 = OpLabel
+%35 = OpIAdd %int %int_1 %int_2
+OpBranch %36
+%36 = OpLabel
+%38 = OpIAdd %int %37 %int_1
+OpBranch %33
+%39 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%main = OpFunction %void None %4
+%12 = OpLabel
+%15 = OpIAdd %int %int_1 %int_2
+OpBranch %13
+%13 = OpLabel
+%14 = OpPhi %int %int_0 %12 %15 %16
+%17 = OpPhi %int %int_0 %12 %18 %16
+OpLoopMerge %19 %16 None
+OpBranch %20
+%20 = OpLabel
+%21 = OpSLessThan %bool %17 %int_10
+OpBranchConditional %21 %22 %19
+%22 = OpLabel
+OpBranch %16
+%16 = OpLabel
+%18 = OpIAdd %int %17 %int_1
+OpBranch %13
+%19 = OpLabel
+%25 = OpIAdd %int %int_1 %int_2
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %int %14 %19 %25 %26
+%27 = OpPhi %int %int_0 %19 %28 %26
+OpLoopMerge %29 %26 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpSLessThan %bool %27 %int_10
+OpBranchConditional %31 %32 %29
+%32 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%28 = OpIAdd %int %27 %int_1
+OpBranch %23
+%29 = OpLabel
+%35 = OpIAdd %int %int_1 %int_2
+OpBranch %33
+%33 = OpLabel
+%34 = OpPhi %int %int_0 %29 %35 %36
+%37 = OpPhi %int %int_0 %29 %38 %36
+OpLoopMerge %39 %36 None
+OpBranch %40
+%40 = OpLabel
+%41 = OpSLessThan %bool %37 %int_10
+OpBranchConditional %41 %42 %39
+%42 = OpLabel
+OpBranch %36
+%36 = OpLabel
+%38 = OpIAdd %int %37 %int_1
+OpBranch %33
+%39 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LICMPass>(before_hoist, after_hoist, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_simple_case.cpp b/test/opt/loop_optimizations/hoist_simple_case.cpp
new file mode 100644
index 0000000..e973d9d
--- /dev/null
+++ b/test/opt/loop_optimizations/hoist_simple_case.cpp
@@ -0,0 +1,126 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "source/opt/licm_pass.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ A simple test for the LICM pass
+
+ Generated from the following GLSL fragment shader
+--eliminate-local-multi-store has also been run on the spv binary
+#version 440 core
+void main(){
+ int a = 1;
+ int b = 2;
+ int hoist = 0;
+ for (int i = 0; i < 10; i++) {
+ // invariant
+ hoist = a + b;
+ }
+}
+*/
+TEST_F(PassClassTest, SimpleHoist) {
+ const std::string before_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%main = OpFunction %void None %4
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+%14 = OpPhi %int %int_0 %12 %15 %16
+%17 = OpPhi %int %int_0 %12 %18 %16
+OpLoopMerge %19 %16 None
+OpBranch %20
+%20 = OpLabel
+%21 = OpSLessThan %bool %17 %int_10
+OpBranchConditional %21 %22 %19
+%22 = OpLabel
+%15 = OpIAdd %int %int_1 %int_2
+OpBranch %16
+%16 = OpLabel
+%18 = OpIAdd %int %17 %int_1
+OpBranch %13
+%19 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%main = OpFunction %void None %4
+%12 = OpLabel
+%15 = OpIAdd %int %int_1 %int_2
+OpBranch %13
+%13 = OpLabel
+%14 = OpPhi %int %int_0 %12 %15 %16
+%17 = OpPhi %int %int_0 %12 %18 %16
+OpLoopMerge %19 %16 None
+OpBranch %20
+%20 = OpLabel
+%21 = OpSLessThan %bool %17 %int_10
+OpBranchConditional %21 %22 %19
+%22 = OpLabel
+OpBranch %16
+%16 = OpLabel
+%18 = OpIAdd %int %17 %int_1
+OpBranch %13
+%19 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LICMPass>(before_hoist, after_hoist, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_single_nested_loops.cpp b/test/opt/loop_optimizations/hoist_single_nested_loops.cpp
new file mode 100644
index 0000000..056f3f0
--- /dev/null
+++ b/test/opt/loop_optimizations/hoist_single_nested_loops.cpp
@@ -0,0 +1,209 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "source/opt/licm_pass.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Tests that the LICM pass will detect an move an invariant from a nested loop,
+ but not it's parent loop
+
+ Generated from the following GLSL fragment shader
+--eliminate-local-multi-store has also been run on the spv binary
+#version 440 core
+void main(){
+ int a = 2;
+ int hoist = 0;
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ // hoist 'hoist = a - i' out of j loop, but not i loop
+ hoist = a - i;
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, NestedSingleHoist) {
+ const std::string before_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_1 = OpConstant %int 1
+%12 = OpUndef %int
+%main = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+%18 = OpPhi %int %int_0 %13 %19 %17
+%20 = OpPhi %int %12 %13 %21 %17
+OpLoopMerge %22 %17 None
+OpBranch %23
+%23 = OpLabel
+%24 = OpSLessThan %bool %18 %int_10
+OpBranchConditional %24 %25 %22
+%25 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%16 = OpPhi %int %15 %25 %27 %28
+%21 = OpPhi %int %int_0 %25 %29 %28
+OpLoopMerge %30 %28 None
+OpBranch %31
+%31 = OpLabel
+%32 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %32 %33 %30
+%33 = OpLabel
+%27 = OpISub %int %int_2 %18
+OpBranch %28
+%28 = OpLabel
+%29 = OpIAdd %int %21 %int_1
+OpBranch %26
+%30 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%19 = OpIAdd %int %18 %int_1
+OpBranch %14
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after_hoist = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_1 = OpConstant %int 1
+%12 = OpUndef %int
+%main = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+%18 = OpPhi %int %int_0 %13 %19 %17
+%20 = OpPhi %int %12 %13 %21 %17
+OpLoopMerge %22 %17 None
+OpBranch %23
+%23 = OpLabel
+%24 = OpSLessThan %bool %18 %int_10
+OpBranchConditional %24 %25 %22
+%25 = OpLabel
+%27 = OpISub %int %int_2 %18
+OpBranch %26
+%26 = OpLabel
+%16 = OpPhi %int %15 %25 %27 %28
+%21 = OpPhi %int %int_0 %25 %29 %28
+OpLoopMerge %30 %28 None
+OpBranch %31
+%31 = OpLabel
+%32 = OpSLessThan %bool %21 %int_10
+OpBranchConditional %32 %33 %30
+%33 = OpLabel
+OpBranch %28
+%28 = OpLabel
+%29 = OpIAdd %int %21 %int_1
+OpBranch %26
+%30 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%19 = OpIAdd %int %18 %int_1
+OpBranch %14
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<LICMPass>(before_hoist, after_hoist, true);
+}
+
+TEST_F(PassClassTest, PreHeaderIsAlsoHeader) {
+ // Move OpSLessThan out of the inner loop. The preheader for the inner loop
+ // is the header of the outer loop. The loop merge should not be separated
+ // from the branch in that block.
+ const std::string text = R"(
+ ; CHECK: OpFunction
+ ; CHECK-NEXT: OpLabel
+ ; CHECK-NEXT: OpBranch [[header:%\w+]]
+ ; CHECK: [[header]] = OpLabel
+ ; CHECK-NEXT: OpSLessThan %bool %int_1 %int_1
+ ; CHECK-NEXT: OpLoopMerge
+ 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
+ %int_1 = OpConstant %int 1
+ %bool = OpTypeBool
+ %2 = OpFunction %void None %4
+ %18 = OpLabel
+ OpBranch %21
+ %21 = OpLabel
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpSLessThan %bool %int_1 %int_1
+ OpLoopMerge %26 %27 None
+ OpBranchConditional %25 %27 %26
+ %27 = OpLabel
+ OpBranch %24
+ %26 = OpLabel
+ OpBranch %22
+ %23 = OpLabel
+ OpBranch %21
+ %22 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LICMPass>(text, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/hoist_without_preheader.cpp b/test/opt/loop_optimizations/hoist_without_preheader.cpp
new file mode 100644
index 0000000..2e34b01
--- /dev/null
+++ b/test/opt/loop_optimizations/hoist_without_preheader.cpp
@@ -0,0 +1,197 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "source/opt/licm_pass.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+ Tests that the LICM pass will generate a preheader when one is not present
+
+ Generated from the following GLSL fragment shader
+--eliminate-local-multi-store has also been run on the spv binary
+#version 440 core
+void main(){
+ int a = 1;
+ int b = 2;
+ int hoist = 0;
+ for (int i = 0; i < 10; i++) {
+ if (i == 5) {
+ break;
+ }
+ }
+ for (int i = 0; i < 10; i++) {
+ hoist = a + b;
+ }
+}
+*/
+TEST_F(PassClassTest, HoistWithoutPreheader) {
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_5 = OpConstant %int 5
+%main = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+; CHECK: OpLoopMerge [[preheader:%\w+]]
+OpLoopMerge %25 %17 None
+OpBranch %19
+%19 = OpLabel
+%20 = OpSLessThan %bool %15 %int_10
+OpBranchConditional %20 %21 %25
+%21 = OpLabel
+%22 = OpIEqual %bool %15 %int_5
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %23
+%24 = OpLabel
+OpBranch %25
+%23 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%16 = OpIAdd %int %15 %int_1
+OpBranch %14
+; Check that we hoisted the code to the preheader
+; CHECK: [[preheader]] = OpLabel
+; CHECK-NEXT: OpPhi
+; CHECK-NEXT: OpPhi
+; CHECK-NEXT: OpIAdd
+; CHECK-NEXT: OpBranch [[header:%\w+]]
+; CHECK: [[header]] = OpLabel
+; CHECK-NEXT: OpPhi
+; CHECK-NEXT: OpPhi
+; CHECK: OpLoopMerge
+%25 = OpLabel
+%26 = OpPhi %int %int_0 %24 %int_0 %19 %27 %28
+%29 = OpPhi %int %int_0 %24 %int_0 %19 %30 %28
+OpLoopMerge %31 %28 None
+OpBranch %32
+%32 = OpLabel
+%33 = OpSLessThan %bool %29 %int_10
+OpBranchConditional %33 %34 %31
+%34 = OpLabel
+%27 = OpIAdd %int %int_1 %int_2
+OpBranch %28
+%28 = OpLabel
+%30 = OpIAdd %int %29 %int_1
+OpBranch %25
+%31 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LICMPass>(text, false);
+}
+
+TEST_F(PassClassTest, HoistWithoutPreheaderAtIdBound) {
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 440
+OpName %main "main"
+%void = OpTypeVoid
+%4 = OpTypeFunction %void
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_1 = OpConstant %int 1
+%int_2 = OpConstant %int 2
+%int_0 = OpConstant %int 0
+%int_10 = OpConstant %int 10
+%bool = OpTypeBool
+%int_5 = OpConstant %int 5
+%main = OpFunction %void None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %int %int_0 %13 %16 %17
+OpLoopMerge %25 %17 None
+OpBranch %19
+%19 = OpLabel
+%20 = OpSLessThan %bool %15 %int_10
+OpBranchConditional %20 %21 %25
+%21 = OpLabel
+%22 = OpIEqual %bool %15 %int_5
+OpSelectionMerge %23 None
+OpBranchConditional %22 %24 %23
+%24 = OpLabel
+OpBranch %25
+%23 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%16 = OpIAdd %int %15 %int_1
+OpBranch %14
+%25 = OpLabel
+%26 = OpPhi %int %int_0 %24 %int_0 %19 %27 %28
+%29 = OpPhi %int %int_0 %24 %int_0 %19 %30 %28
+OpLoopMerge %31 %28 None
+OpBranch %32
+%32 = OpLabel
+%33 = OpSLessThan %bool %29 %int_10
+OpBranchConditional %33 %34 %31
+%34 = OpLabel
+%27 = OpIAdd %int %int_1 %int_2
+OpBranch %28
+%28 = OpLabel
+%30 = OpIAdd %int %29 %int_1
+OpBranch %25
+%31 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound);
+
+ auto pass = MakeUnique<LICMPass>();
+ auto result = pass->Run(context.get());
+ EXPECT_EQ(result, Pass::Status::Failure);
+
+ std::vector<uint32_t> binary;
+ context->module()->ToBinary(&binary, false);
+ std::string optimized_asm;
+ SpirvTools tools_(SPV_ENV_UNIVERSAL_1_1);
+ tools_.Disassemble(binary, &optimized_asm);
+ std::cout << optimized_asm << std::endl;
+}
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/lcssa.cpp b/test/opt/loop_optimizations/lcssa.cpp
new file mode 100644
index 0000000..ace6ce1
--- /dev/null
+++ b/test/opt/loop_optimizations/lcssa.cpp
@@ -0,0 +1,607 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "source/opt/build_module.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/loop_utils.h"
+#include "source/opt/pass.h"
+#include "test/opt//assembly_builder.h"
+#include "test/opt/function_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+bool Validate(const std::vector<uint32_t>& bin) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {bin.data(), bin.size()};
+ spv_result_t error = spvValidate(spvContext, &binary, &diagnostic);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ return error == 0;
+}
+
+void Match(const std::string& original, IRContext* context,
+ bool do_validation = true) {
+ std::vector<uint32_t> bin;
+ context->module()->ToBinary(&bin, true);
+ if (do_validation) {
+ EXPECT_TRUE(Validate(bin));
+ }
+ std::string assembly;
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_2);
+ EXPECT_TRUE(
+ tools.Disassemble(bin, &assembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER))
+ << "Disassembling failed for shader:\n"
+ << assembly << std::endl;
+ auto match_result = effcee::Match(assembly, original);
+ EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+ << match_result.message() << "\nChecking result:\n"
+ << assembly;
+}
+
+using LCSSATest = ::testing::Test;
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ int i = 0;
+ for (; i < 10; i++) {
+ }
+ if (i != 0) {
+ i = 1;
+ }
+}
+*/
+TEST_F(LCSSATest, SimpleLCSSA) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] %19 None
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %30 %20
+; CHECK-NEXT: %27 = OpINotEqual {{%\w+}} [[phi]] %9
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeInt 32 1
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %10 = OpConstant %7 10
+ %11 = OpTypeBool
+ %12 = OpConstant %7 1
+ %13 = OpTypeFloat 32
+ %14 = OpTypeVector %13 4
+ %15 = OpTypePointer Output %14
+ %3 = OpVariable %15 Output
+ %2 = OpFunction %5 None %6
+ %16 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ %30 = OpPhi %7 %9 %16 %25 %19
+ OpLoopMerge %18 %19 None
+ OpBranch %20
+ %20 = OpLabel
+ %22 = OpSLessThan %11 %30 %10
+ OpBranchConditional %22 %23 %18
+ %23 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %25 = OpIAdd %7 %30 %12
+ OpBranch %17
+ %18 = OpLabel
+ %27 = OpINotEqual %11 %30 %9
+ OpSelectionMerge %28 None
+ OpBranchConditional %27 %29 %28
+ %29 = OpLabel
+ OpBranch %28
+ %28 = OpLabel
+ %31 = OpPhi %7 %30 %18 %12 %29
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ Loop* loop = ld[17];
+ EXPECT_FALSE(loop->IsLCSSA());
+ LoopUtils Util(context.get(), loop);
+ Util.MakeLoopClosedSSA();
+ EXPECT_TRUE(loop->IsLCSSA());
+ Match(text, context.get());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ int i = 0;
+ for (; i < 10; i++) {
+ }
+ if (i != 0) {
+ i = 1;
+ }
+}
+*/
+// Same test as above, but should reuse an existing phi.
+TEST_F(LCSSATest, PhiReuseLCSSA) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] %19 None
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %30 %20
+; CHECK-NEXT: %27 = OpINotEqual {{%\w+}} [[phi]] %9
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeInt 32 1
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %10 = OpConstant %7 10
+ %11 = OpTypeBool
+ %12 = OpConstant %7 1
+ %13 = OpTypeFloat 32
+ %14 = OpTypeVector %13 4
+ %15 = OpTypePointer Output %14
+ %3 = OpVariable %15 Output
+ %2 = OpFunction %5 None %6
+ %16 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ %30 = OpPhi %7 %9 %16 %25 %19
+ OpLoopMerge %18 %19 None
+ OpBranch %20
+ %20 = OpLabel
+ %22 = OpSLessThan %11 %30 %10
+ OpBranchConditional %22 %23 %18
+ %23 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %25 = OpIAdd %7 %30 %12
+ OpBranch %17
+ %18 = OpLabel
+ %32 = OpPhi %7 %30 %20
+ %27 = OpINotEqual %11 %30 %9
+ OpSelectionMerge %28 None
+ OpBranchConditional %27 %29 %28
+ %29 = OpLabel
+ OpBranch %28
+ %28 = OpLabel
+ %31 = OpPhi %7 %30 %18 %12 %29
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ Loop* loop = ld[17];
+ EXPECT_FALSE(loop->IsLCSSA());
+ LoopUtils Util(context.get(), loop);
+ Util.MakeLoopClosedSSA();
+ EXPECT_TRUE(loop->IsLCSSA());
+ Match(text, context.get());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ int i = 0;
+ int j = 0;
+ for (; i < 10; i++) {}
+ for (; j < 10; j++) {}
+ if (j != 0) {
+ i = 1;
+ }
+}
+*/
+TEST_F(LCSSATest, DualLoopLCSSA) {
+ const std::string text = R"(
+; CHECK: %20 = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi %6 %17 %21
+; CHECK: %33 = OpLabel
+; CHECK-NEXT: {{%\w+}} = OpPhi {{%\w+}} [[phi]] %28 %11 %34
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpConstant %6 0
+ %9 = OpConstant %6 10
+ %10 = OpTypeBool
+ %11 = OpConstant %6 1
+ %12 = OpTypeFloat 32
+ %13 = OpTypeVector %12 4
+ %14 = OpTypePointer Output %13
+ %3 = OpVariable %14 Output
+ %2 = OpFunction %4 None %5
+ %15 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %17 = OpPhi %6 %8 %15 %18 %19
+ OpLoopMerge %20 %19 None
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpSLessThan %10 %17 %9
+ OpBranchConditional %22 %23 %20
+ %23 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %18 = OpIAdd %6 %17 %11
+ OpBranch %16
+ %20 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpPhi %6 %8 %20 %26 %27
+ OpLoopMerge %28 %27 None
+ OpBranch %29
+ %29 = OpLabel
+ %30 = OpSLessThan %10 %25 %9
+ OpBranchConditional %30 %31 %28
+ %31 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %26 = OpIAdd %6 %25 %11
+ OpBranch %24
+ %28 = OpLabel
+ %32 = OpINotEqual %10 %25 %8
+ OpSelectionMerge %33 None
+ OpBranchConditional %32 %34 %33
+ %34 = OpLabel
+ OpBranch %33
+ %33 = OpLabel
+ %35 = OpPhi %6 %17 %28 %11 %34
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ Loop* loop = ld[16];
+ EXPECT_FALSE(loop->IsLCSSA());
+ LoopUtils Util(context.get(), loop);
+ Util.MakeLoopClosedSSA();
+ EXPECT_TRUE(loop->IsLCSSA());
+ Match(text, context.get());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ int i = 0;
+ if (i != 0) {
+ for (; i < 10; i++) {}
+ }
+ if (i != 0) {
+ i = 1;
+ }
+}
+*/
+TEST_F(LCSSATest, PhiUserLCSSA) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] %22 None
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %20 %24
+; CHECK: %17 = OpLabel
+; CHECK-NEXT: {{%\w+}} = OpPhi {{%\w+}} %8 %15 [[phi]] %23
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %4 = OpTypeVoid
+ %5 = OpTypeFunction %4
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpConstant %6 0
+ %9 = OpTypeBool
+ %10 = OpConstant %6 10
+ %11 = OpConstant %6 1
+ %12 = OpTypeFloat 32
+ %13 = OpTypeVector %12 4
+ %14 = OpTypePointer Output %13
+ %3 = OpVariable %14 Output
+ %2 = OpFunction %4 None %5
+ %15 = OpLabel
+ %16 = OpINotEqual %9 %8 %8
+ OpSelectionMerge %17 None
+ OpBranchConditional %16 %18 %17
+ %18 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %20 = OpPhi %6 %8 %18 %21 %22
+ OpLoopMerge %23 %22 None
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpSLessThan %9 %20 %10
+ OpBranchConditional %25 %26 %23
+ %26 = OpLabel
+ OpBranch %22
+ %22 = OpLabel
+ %21 = OpIAdd %6 %20 %11
+ OpBranch %19
+ %23 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ %27 = OpPhi %6 %8 %15 %20 %23
+ %28 = OpINotEqual %9 %27 %8
+ OpSelectionMerge %29 None
+ OpBranchConditional %28 %30 %29
+ %30 = OpLabel
+ OpBranch %29
+ %29 = OpLabel
+ %31 = OpPhi %6 %27 %17 %11 %30
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ Loop* loop = ld[19];
+ EXPECT_FALSE(loop->IsLCSSA());
+ LoopUtils Util(context.get(), loop);
+ Util.MakeLoopClosedSSA();
+ EXPECT_TRUE(loop->IsLCSSA());
+ Match(text, context.get());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int i = 0;
+ if (i != 0) {
+ for (; i < 10; i++) {
+ if (i > 5) break;
+ }
+ }
+ if (i != 0) {
+ i = 1;
+ }
+}
+*/
+TEST_F(LCSSATest, LCSSAWithBreak) {
+ const std::string text = R"(
+; CHECK: OpLoopMerge [[merge:%\w+]] %19 None
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[phi:%\w+]] = OpPhi {{%\w+}} %17 %21 %17 %26
+; CHECK: %14 = OpLabel
+; CHECK-NEXT: {{%\w+}} = OpPhi {{%\w+}} %7 %12 [[phi]] [[merge]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %6 = OpTypePointer Function %5
+ %7 = OpConstant %5 0
+ %8 = OpTypeBool
+ %9 = OpConstant %5 10
+ %10 = OpConstant %5 5
+ %11 = OpConstant %5 1
+ %2 = OpFunction %3 None %4
+ %12 = OpLabel
+ %13 = OpINotEqual %8 %7 %7
+ OpSelectionMerge %14 None
+ OpBranchConditional %13 %15 %14
+ %15 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %17 = OpPhi %5 %7 %15 %18 %19
+ OpLoopMerge %20 %19 None
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpSLessThan %8 %17 %9
+ OpBranchConditional %22 %23 %20
+ %23 = OpLabel
+ %24 = OpSGreaterThan %8 %17 %10
+ OpSelectionMerge %25 None
+ OpBranchConditional %24 %26 %25
+ %26 = OpLabel
+ OpBranch %20
+ %25 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %18 = OpIAdd %5 %17 %11
+ OpBranch %16
+ %20 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ %27 = OpPhi %5 %7 %12 %17 %20
+ %28 = OpINotEqual %8 %27 %7
+ OpSelectionMerge %29 None
+ OpBranchConditional %28 %30 %29
+ %30 = OpLabel
+ OpBranch %29
+ %29 = OpLabel
+ %31 = OpPhi %5 %27 %14 %11 %30
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ Loop* loop = ld[19];
+ EXPECT_FALSE(loop->IsLCSSA());
+ LoopUtils Util(context.get(), loop);
+ Util.MakeLoopClosedSSA();
+ EXPECT_TRUE(loop->IsLCSSA());
+ Match(text, context.get());
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int i = 0;
+ for (; i < 10; i++) {}
+ for (int j = i; j < 10;) { j = i + j; }
+}
+*/
+TEST_F(LCSSATest, LCSSAUseInNonEligiblePhi) {
+ const std::string text = R"(
+; CHECK: %12 = OpLabel
+; CHECK-NEXT: [[def_to_close:%\w+]] = OpPhi {{%\w+}} {{%\w+}} {{%\w+}} {{%\w+}} [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: [[closing_phi:%\w+]] = OpPhi {{%\w+}} [[def_to_close]] %17
+; CHECK: %16 = OpLabel
+; CHECK-NEXT: [[use_in_phi:%\w+]] = OpPhi {{%\w+}} %21 %22 [[closing_phi]] [[merge]]
+; CHECK: OpIAdd {{%\w+}} [[closing_phi]] [[use_in_phi]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %6 = OpTypePointer Function %5
+ %7 = OpConstant %5 0
+ %8 = OpConstant %5 10
+ %9 = OpTypeBool
+ %10 = OpConstant %5 1
+ %2 = OpFunction %3 None %4
+ %11 = OpLabel
+ OpBranch %12
+ %12 = OpLabel
+ %13 = OpPhi %5 %7 %11 %14 %15
+ OpLoopMerge %16 %15 None
+ OpBranch %17
+ %17 = OpLabel
+ %18 = OpSLessThan %9 %13 %8
+ OpBranchConditional %18 %19 %16
+ %19 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ %14 = OpIAdd %5 %13 %10
+ OpBranch %12
+ %16 = OpLabel
+ %20 = OpPhi %5 %13 %17 %21 %22
+ OpLoopMerge %23 %22 None
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpSLessThan %9 %20 %8
+ OpBranchConditional %25 %26 %23
+ %26 = OpLabel
+ %21 = OpIAdd %5 %13 %20
+ OpBranch %22
+ %22 = OpLabel
+ OpBranch %16
+ %23 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ Loop* loop = ld[12];
+ EXPECT_FALSE(loop->IsLCSSA());
+ LoopUtils Util(context.get(), loop);
+ Util.MakeLoopClosedSSA();
+ EXPECT_TRUE(loop->IsLCSSA());
+ Match(text, context.get());
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/loop_descriptions.cpp b/test/opt/loop_optimizations/loop_descriptions.cpp
new file mode 100644
index 0000000..91dbdc6
--- /dev/null
+++ b/test/opt/loop_optimizations/loop_descriptions.cpp
@@ -0,0 +1,384 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ int i = 0;
+ for(; i < 10; ++i) {
+ }
+}
+*/
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %5 "i"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpConstant %8 1
+ %14 = OpTypeFloat 32
+ %15 = OpTypeVector %14 4
+ %16 = OpTypePointer Output %15
+ %3 = OpVariable %16 Output
+ %2 = OpFunction %6 None %7
+ %17 = OpLabel
+ %5 = OpVariable %9 Function
+ OpStore %5 %10
+ OpBranch %18
+ %18 = OpLabel
+ OpLoopMerge %19 %20 None
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpLoad %8 %5
+ %23 = OpSLessThan %12 %22 %11
+ OpBranchConditional %23 %24 %19
+ %24 = OpLabel
+ OpBranch %20
+ %20 = OpLabel
+ %25 = OpLoad %8 %5
+ %26 = OpIAdd %8 %25 %13
+ OpStore %5 %26
+ OpBranch %18
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ Loop& loop = ld.GetLoopByIndex(0);
+ EXPECT_EQ(loop.GetHeaderBlock(), spvtest::GetBasicBlock(f, 18));
+ EXPECT_EQ(loop.GetLatchBlock(), spvtest::GetBasicBlock(f, 20));
+ EXPECT_EQ(loop.GetMergeBlock(), spvtest::GetBasicBlock(f, 19));
+
+ EXPECT_FALSE(loop.HasNestedLoops());
+ EXPECT_FALSE(loop.IsNested());
+ EXPECT_EQ(loop.GetDepth(), 1u);
+}
+
+/*
+Generated from the following GLSL:
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ for(int i = 0; i < 10; ++i) {}
+ for(int i = 0; i < 10; ++i) {}
+}
+
+But it was "hacked" to make the first loop merge block the second loop header.
+*/
+TEST_F(PassClassTest, LoopWithNoPreHeader) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ OpName %5 "i"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpConstant %8 1
+ %14 = OpTypeFloat 32
+ %15 = OpTypeVector %14 4
+ %16 = OpTypePointer Output %15
+ %3 = OpVariable %16 Output
+ %2 = OpFunction %6 None %7
+ %17 = OpLabel
+ %4 = OpVariable %9 Function
+ %5 = OpVariable %9 Function
+ OpStore %4 %10
+ OpStore %5 %10
+ OpBranch %18
+ %18 = OpLabel
+ OpLoopMerge %27 %20 None
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpLoad %8 %4
+ %23 = OpSLessThan %12 %22 %11
+ OpBranchConditional %23 %24 %27
+ %24 = OpLabel
+ OpBranch %20
+ %20 = OpLabel
+ %25 = OpLoad %8 %4
+ %26 = OpIAdd %8 %25 %13
+ OpStore %4 %26
+ OpBranch %18
+ %27 = OpLabel
+ OpLoopMerge %28 %29 None
+ OpBranch %30
+ %30 = OpLabel
+ %31 = OpLoad %8 %5
+ %32 = OpSLessThan %12 %31 %11
+ OpBranchConditional %32 %33 %28
+ %33 = OpLabel
+ OpBranch %29
+ %29 = OpLabel
+ %34 = OpLoad %8 %5
+ %35 = OpIAdd %8 %34 %13
+ OpStore %5 %35
+ OpBranch %27
+ %28 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ Loop* loop = ld[27];
+ EXPECT_EQ(loop->GetPreHeaderBlock(), nullptr);
+ EXPECT_NE(loop->GetOrCreatePreHeaderBlock(), nullptr);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+in vec4 c;
+void main() {
+ int i = 0;
+ bool cond = c[0] == 0;
+ for (; i < 10; i++) {
+ if (cond) {
+ return;
+ }
+ else {
+ return;
+ }
+ }
+ bool cond2 = i == 9;
+}
+*/
+TEST_F(PassClassTest, NoLoop) {
+ const std::string text = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 3
+; Bound: 47
+; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %16
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %4 "main"
+ OpName %16 "c"
+ OpDecorate %16 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %10 = OpTypeBool
+ %11 = OpTypePointer Function %10
+ %13 = OpTypeFloat 32
+ %14 = OpTypeVector %13 4
+ %15 = OpTypePointer Input %14
+ %16 = OpVariable %15 Input
+ %17 = OpTypeInt 32 0
+ %18 = OpConstant %17 0
+ %19 = OpTypePointer Input %13
+ %22 = OpConstant %13 0
+ %30 = OpConstant %6 10
+ %39 = OpConstant %6 1
+ %46 = OpUndef %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %20 = OpAccessChain %19 %16 %18
+ %21 = OpLoad %13 %20
+ %23 = OpFOrdEqual %10 %21 %22
+ OpBranch %24
+ %24 = OpLabel
+ %45 = OpPhi %6 %9 %5 %40 %27
+ OpLoopMerge %26 %27 None
+ OpBranch %28
+ %28 = OpLabel
+ %31 = OpSLessThan %10 %45 %30
+ OpBranchConditional %31 %25 %26
+ %25 = OpLabel
+ OpSelectionMerge %34 None
+ OpBranchConditional %23 %33 %36
+ %33 = OpLabel
+ OpReturn
+ %36 = OpLabel
+ OpReturn
+ %34 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %40 = OpIAdd %6 %46 %39
+ OpBranch %24
+ %26 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor ld{context.get(), f};
+
+ EXPECT_EQ(ld.NumLoops(), 0u);
+}
+
+/*
+Generated from following GLSL with latch block artificially inserted to be
+seperate from continue.
+#version 430
+void main(void) {
+ float x[10];
+ for (int i = 0; i < 10; ++i) {
+ x[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, LoopLatchNotContinue) {
+ const std::string text = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "x"
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeInt 32 1
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %10 = OpConstant %7 10
+ %11 = OpTypeBool
+ %12 = OpTypeFloat 32
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 10
+ %15 = OpTypeArray %12 %14
+ %16 = OpTypePointer Function %15
+ %17 = OpTypePointer Function %12
+ %18 = OpConstant %7 1
+ %2 = OpFunction %5 None %6
+ %19 = OpLabel
+ %3 = OpVariable %8 Function
+ %4 = OpVariable %16 Function
+ OpStore %3 %9
+ OpBranch %20
+ %20 = OpLabel
+ %21 = OpPhi %7 %9 %19 %22 %30
+ OpLoopMerge %24 %23 None
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpSLessThan %11 %21 %10
+ OpBranchConditional %26 %27 %24
+ %27 = OpLabel
+ %28 = OpConvertSToF %12 %21
+ %29 = OpAccessChain %17 %4 %21
+ OpStore %29 %28
+ OpBranch %23
+ %23 = OpLabel
+ %22 = OpIAdd %7 %21 %18
+ OpStore %3 %22
+ OpBranch %30
+ %30 = OpLabel
+ OpBranch %20
+ %24 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ Loop& loop = ld.GetLoopByIndex(0u);
+
+ EXPECT_NE(loop.GetLatchBlock(), loop.GetContinueBlock());
+
+ EXPECT_EQ(loop.GetContinueBlock()->id(), 23u);
+ EXPECT_EQ(loop.GetLatchBlock()->id(), 30u);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/loop_fission.cpp b/test/opt/loop_optimizations/loop_fission.cpp
new file mode 100644
index 0000000..e513f42
--- /dev/null
+++ b/test/opt/loop_optimizations/loop_fission.cpp
@@ -0,0 +1,3491 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/loop_fission.h"
+#include "source/opt/loop_unroller.h"
+#include "source/opt/loop_utils.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using FissionClassTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL
+
+#version 430
+
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ A[i] = B[i];
+ B[i] = A[i];
+ }
+}
+
+Result should be equivalent to:
+
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ A[i] = B[i];
+ }
+
+ for (int i = 0; i < 10; i++) {
+ B[i] = A[i];
+ }
+}
+*/
+TEST_F(FissionClassTest, SimpleFission) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "A"
+OpName %5 "B"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %20 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%29 = OpAccessChain %18 %5 %22
+%30 = OpLoad %13 %29
+%31 = OpAccessChain %18 %4 %22
+OpStore %31 %30
+%32 = OpAccessChain %18 %4 %22
+%33 = OpLoad %13 %32
+%34 = OpAccessChain %18 %5 %22
+OpStore %34 %33
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "A"
+OpName %5 "B"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpBranch %35
+%35 = OpLabel
+%36 = OpPhi %8 %10 %20 %47 %46
+OpLoopMerge %48 %46 None
+OpBranch %37
+%37 = OpLabel
+%38 = OpSLessThan %12 %36 %11
+OpBranchConditional %38 %39 %48
+%39 = OpLabel
+%40 = OpAccessChain %18 %5 %36
+%41 = OpLoad %13 %40
+%42 = OpAccessChain %18 %4 %36
+OpStore %42 %41
+OpBranch %46
+%46 = OpLabel
+%47 = OpIAdd %8 %36 %19
+OpBranch %35
+%48 = OpLabel
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %48 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%32 = OpAccessChain %18 %4 %22
+%33 = OpLoad %13 %32
+%34 = OpAccessChain %18 %5 %22
+OpStore %34 %33
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+
+ // Check that the loop will NOT be split when provided with a pass-through
+ // register pressure functor which just returns false.
+ SinglePassRunAndCheck<LoopFissionPass>(
+ source, source, true,
+ [](const RegisterLiveness::RegionRegisterLiveness&) { return false; });
+}
+
+/*
+Generated from the following GLSL
+
+#version 430
+
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ A[i] = B[i];
+ B[i] = A[i+1];
+ }
+}
+
+This loop should not be split, as the i+1 dependence would be broken by
+splitting the loop.
+*/
+
+TEST_F(FissionClassTest, FissionInterdependency) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "A"
+OpName %5 "B"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %20 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%29 = OpAccessChain %18 %5 %22
+%30 = OpLoad %13 %29
+%31 = OpAccessChain %18 %4 %22
+OpStore %31 %30
+%32 = OpIAdd %8 %22 %19
+%33 = OpAccessChain %18 %4 %32
+%34 = OpLoad %13 %33
+%35 = OpAccessChain %18 %5 %22
+OpStore %35 %34
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+Generated from the following GLSL
+
+#version 430
+
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ A[i] = B[i];
+ B[i+1] = A[i];
+ }
+}
+
+
+This should not be split as the load B[i] is dependent on the store B[i+1]
+*/
+TEST_F(FissionClassTest, FissionInterdependency2) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "A"
+OpName %5 "B"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %20 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%29 = OpAccessChain %18 %5 %22
+%30 = OpLoad %13 %29
+%31 = OpAccessChain %18 %4 %22
+OpStore %31 %30
+%32 = OpIAdd %8 %22 %19
+%33 = OpAccessChain %18 %4 %22
+%34 = OpLoad %13 %33
+%35 = OpAccessChain %18 %5 %32
+OpStore %35 %34
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10];
+ float B[10];
+ float C[10]
+ float D[10]
+ for (int i = 0; i < 10; i++) {
+ A[i] = B[i];
+ B[i] = A[i];
+ C[i] = D[i];
+ D[i] = C[i];
+ }
+}
+
+This should be split into the equivalent of:
+
+ for (int i = 0; i < 10; i++) {
+ A[i] = B[i];
+ B[i] = A[i];
+ }
+ for (int i = 0; i < 10; i++) {
+ C[i] = D[i];
+ D[i] = C[i];
+ }
+
+We then check that the loop is broken into four for loops like so, if the pass
+is run twice:
+ for (int i = 0; i < 10; i++)
+ A[i] = B[i];
+ for (int i = 0; i < 10; i++)
+ B[i] = A[i];
+ for (int i = 0; i < 10; i++)
+ C[i] = D[i];
+ for (int i = 0; i < 10; i++)
+ D[i] = C[i];
+
+*/
+
+TEST_F(FissionClassTest, FissionMultipleLoadStores) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string source = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "A"
+ OpName %5 "B"
+ OpName %6 "C"
+ OpName %7 "D"
+ %8 = OpTypeVoid
+ %9 = OpTypeFunction %8
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %12 = OpConstant %10 0
+ %13 = OpConstant %10 10
+ %14 = OpTypeBool
+ %15 = OpTypeFloat 32
+ %16 = OpTypeInt 32 0
+ %17 = OpConstant %16 10
+ %18 = OpTypeArray %15 %17
+ %19 = OpTypePointer Function %18
+ %20 = OpTypePointer Function %15
+ %21 = OpConstant %10 1
+ %2 = OpFunction %8 None %9
+ %22 = OpLabel
+ %3 = OpVariable %11 Function
+ %4 = OpVariable %19 Function
+ %5 = OpVariable %19 Function
+ %6 = OpVariable %19 Function
+ %7 = OpVariable %19 Function
+ OpBranch %23
+ %23 = OpLabel
+ %24 = OpPhi %10 %12 %22 %25 %26
+ OpLoopMerge %27 %26 None
+ OpBranch %28
+ %28 = OpLabel
+ %29 = OpSLessThan %14 %24 %13
+ OpBranchConditional %29 %30 %27
+ %30 = OpLabel
+ %31 = OpAccessChain %20 %5 %24
+ %32 = OpLoad %15 %31
+ %33 = OpAccessChain %20 %4 %24
+ OpStore %33 %32
+ %34 = OpAccessChain %20 %4 %24
+ %35 = OpLoad %15 %34
+ %36 = OpAccessChain %20 %5 %24
+ OpStore %36 %35
+ %37 = OpAccessChain %20 %7 %24
+ %38 = OpLoad %15 %37
+ %39 = OpAccessChain %20 %6 %24
+ OpStore %39 %38
+ %40 = OpAccessChain %20 %6 %24
+ %41 = OpLoad %15 %40
+ %42 = OpAccessChain %20 %7 %24
+ OpStore %42 %41
+ OpBranch %26
+ %26 = OpLabel
+ %25 = OpIAdd %10 %24 %21
+ OpBranch %23
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "A"
+OpName %5 "B"
+OpName %6 "C"
+OpName %7 "D"
+%8 = OpTypeVoid
+%9 = OpTypeFunction %8
+%10 = OpTypeInt 32 1
+%11 = OpTypePointer Function %10
+%12 = OpConstant %10 0
+%13 = OpConstant %10 10
+%14 = OpTypeBool
+%15 = OpTypeFloat 32
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 10
+%18 = OpTypeArray %15 %17
+%19 = OpTypePointer Function %18
+%20 = OpTypePointer Function %15
+%21 = OpConstant %10 1
+%2 = OpFunction %8 None %9
+%22 = OpLabel
+%3 = OpVariable %11 Function
+%4 = OpVariable %19 Function
+%5 = OpVariable %19 Function
+%6 = OpVariable %19 Function
+%7 = OpVariable %19 Function
+OpBranch %43
+%43 = OpLabel
+%44 = OpPhi %10 %12 %22 %61 %60
+OpLoopMerge %62 %60 None
+OpBranch %45
+%45 = OpLabel
+%46 = OpSLessThan %14 %44 %13
+OpBranchConditional %46 %47 %62
+%47 = OpLabel
+%48 = OpAccessChain %20 %5 %44
+%49 = OpLoad %15 %48
+%50 = OpAccessChain %20 %4 %44
+OpStore %50 %49
+%51 = OpAccessChain %20 %4 %44
+%52 = OpLoad %15 %51
+%53 = OpAccessChain %20 %5 %44
+OpStore %53 %52
+OpBranch %60
+%60 = OpLabel
+%61 = OpIAdd %10 %44 %21
+OpBranch %43
+%62 = OpLabel
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %10 %12 %62 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %14 %24 %13
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+%37 = OpAccessChain %20 %7 %24
+%38 = OpLoad %15 %37
+%39 = OpAccessChain %20 %6 %24
+OpStore %39 %38
+%40 = OpAccessChain %20 %6 %24
+%41 = OpLoad %15 %40
+%42 = OpAccessChain %20 %7 %24
+OpStore %42 %41
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %10 %24 %21
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+
+const std::string expected_multiple_passes = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "A"
+OpName %5 "B"
+OpName %6 "C"
+OpName %7 "D"
+%8 = OpTypeVoid
+%9 = OpTypeFunction %8
+%10 = OpTypeInt 32 1
+%11 = OpTypePointer Function %10
+%12 = OpConstant %10 0
+%13 = OpConstant %10 10
+%14 = OpTypeBool
+%15 = OpTypeFloat 32
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 10
+%18 = OpTypeArray %15 %17
+%19 = OpTypePointer Function %18
+%20 = OpTypePointer Function %15
+%21 = OpConstant %10 1
+%2 = OpFunction %8 None %9
+%22 = OpLabel
+%3 = OpVariable %11 Function
+%4 = OpVariable %19 Function
+%5 = OpVariable %19 Function
+%6 = OpVariable %19 Function
+%7 = OpVariable %19 Function
+OpBranch %63
+%63 = OpLabel
+%64 = OpPhi %10 %12 %22 %75 %74
+OpLoopMerge %76 %74 None
+OpBranch %65
+%65 = OpLabel
+%66 = OpSLessThan %14 %64 %13
+OpBranchConditional %66 %67 %76
+%67 = OpLabel
+%68 = OpAccessChain %20 %5 %64
+%69 = OpLoad %15 %68
+%70 = OpAccessChain %20 %4 %64
+OpStore %70 %69
+OpBranch %74
+%74 = OpLabel
+%75 = OpIAdd %10 %64 %21
+OpBranch %63
+%76 = OpLabel
+OpBranch %43
+%43 = OpLabel
+%44 = OpPhi %10 %12 %76 %61 %60
+OpLoopMerge %62 %60 None
+OpBranch %45
+%45 = OpLabel
+%46 = OpSLessThan %14 %44 %13
+OpBranchConditional %46 %47 %62
+%47 = OpLabel
+%51 = OpAccessChain %20 %4 %44
+%52 = OpLoad %15 %51
+%53 = OpAccessChain %20 %5 %44
+OpStore %53 %52
+OpBranch %60
+%60 = OpLabel
+%61 = OpIAdd %10 %44 %21
+OpBranch %43
+%62 = OpLabel
+OpBranch %77
+%77 = OpLabel
+%78 = OpPhi %10 %12 %62 %89 %88
+OpLoopMerge %90 %88 None
+OpBranch %79
+%79 = OpLabel
+%80 = OpSLessThan %14 %78 %13
+OpBranchConditional %80 %81 %90
+%81 = OpLabel
+%82 = OpAccessChain %20 %7 %78
+%83 = OpLoad %15 %82
+%84 = OpAccessChain %20 %6 %78
+OpStore %84 %83
+OpBranch %88
+%88 = OpLabel
+%89 = OpIAdd %10 %78 %21
+OpBranch %77
+%90 = OpLabel
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %10 %12 %90 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %14 %24 %13
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+%40 = OpAccessChain %20 %6 %24
+%41 = OpLoad %15 %40
+%42 = OpAccessChain %20 %7 %24
+OpStore %42 %41
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %10 %24 %21
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+Module* module = context->module();
+EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+
+// By passing 1 as argument we are using the constructor which makes the
+// critera to split the loop be if the registers in the loop exceede 1. By
+// using this constructor we are also enabling multiple passes (disabled by
+// default).
+SinglePassRunAndCheck<LoopFissionPass>(source, expected_multiple_passes, true,
+ 1);
+}
+
+/*
+#version 430
+void main(void) {
+ int accumulator = 0;
+ float X[10];
+ float Y[10];
+
+ for (int i = 0; i < 10; i++) {
+ X[i] = Y[i];
+ Y[i] = X[i];
+ accumulator += i;
+ }
+}
+
+This should be split into the equivalent of:
+
+#version 430
+void main(void) {
+ int accumulator = 0;
+ float X[10];
+ float Y[10];
+
+ for (int i = 0; i < 10; i++) {
+ X[i] = Y[i];
+ }
+ for (int i = 0; i < 10; i++) {
+ Y[i] = X[i];
+ accumulator += i;
+ }
+}
+*/
+TEST_F(FissionClassTest, FissionWithAccumulator) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string source = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "accumulator"
+ OpName %4 "i"
+ OpName %5 "X"
+ OpName %6 "Y"
+ %7 = OpTypeVoid
+ %8 = OpTypeFunction %7
+ %9 = OpTypeInt 32 1
+ %10 = OpTypePointer Function %9
+ %11 = OpConstant %9 0
+ %12 = OpConstant %9 10
+ %13 = OpTypeBool
+ %14 = OpTypeFloat 32
+ %15 = OpTypeInt 32 0
+ %16 = OpConstant %15 10
+ %17 = OpTypeArray %14 %16
+ %18 = OpTypePointer Function %17
+ %19 = OpTypePointer Function %14
+ %20 = OpConstant %9 1
+ %2 = OpFunction %7 None %8
+ %21 = OpLabel
+ %3 = OpVariable %10 Function
+ %4 = OpVariable %10 Function
+ %5 = OpVariable %18 Function
+ %6 = OpVariable %18 Function
+ OpBranch %22
+ %22 = OpLabel
+ %23 = OpPhi %9 %11 %21 %24 %25
+ %26 = OpPhi %9 %11 %21 %27 %25
+ OpLoopMerge %28 %25 None
+ OpBranch %29
+ %29 = OpLabel
+ %30 = OpSLessThan %13 %26 %12
+ OpBranchConditional %30 %31 %28
+ %31 = OpLabel
+ %32 = OpAccessChain %19 %6 %26
+ %33 = OpLoad %14 %32
+ %34 = OpAccessChain %19 %5 %26
+ OpStore %34 %33
+ %35 = OpAccessChain %19 %5 %26
+ %36 = OpLoad %14 %35
+ %37 = OpAccessChain %19 %6 %26
+ OpStore %37 %36
+ %24 = OpIAdd %9 %23 %26
+ OpBranch %25
+ %25 = OpLabel
+ %27 = OpIAdd %9 %26 %20
+ OpBranch %22
+ %28 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "accumulator"
+OpName %4 "i"
+OpName %5 "X"
+OpName %6 "Y"
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpTypeFloat 32
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 10
+%17 = OpTypeArray %14 %16
+%18 = OpTypePointer Function %17
+%19 = OpTypePointer Function %14
+%20 = OpConstant %9 1
+%2 = OpFunction %7 None %8
+%21 = OpLabel
+%3 = OpVariable %10 Function
+%4 = OpVariable %10 Function
+%5 = OpVariable %18 Function
+%6 = OpVariable %18 Function
+OpBranch %38
+%38 = OpLabel
+%40 = OpPhi %9 %11 %21 %52 %51
+OpLoopMerge %53 %51 None
+OpBranch %41
+%41 = OpLabel
+%42 = OpSLessThan %13 %40 %12
+OpBranchConditional %42 %43 %53
+%43 = OpLabel
+%44 = OpAccessChain %19 %6 %40
+%45 = OpLoad %14 %44
+%46 = OpAccessChain %19 %5 %40
+OpStore %46 %45
+OpBranch %51
+%51 = OpLabel
+%52 = OpIAdd %9 %40 %20
+OpBranch %38
+%53 = OpLabel
+OpBranch %22
+%22 = OpLabel
+%23 = OpPhi %9 %11 %53 %24 %25
+%26 = OpPhi %9 %11 %53 %27 %25
+OpLoopMerge %28 %25 None
+OpBranch %29
+%29 = OpLabel
+%30 = OpSLessThan %13 %26 %12
+OpBranchConditional %30 %31 %28
+%31 = OpLabel
+%35 = OpAccessChain %19 %5 %26
+%36 = OpLoad %14 %35
+%37 = OpAccessChain %19 %6 %26
+OpStore %37 %36
+%24 = OpIAdd %9 %23 %26
+OpBranch %25
+%25 = OpLabel
+%27 = OpIAdd %9 %26 %20
+OpBranch %22
+%28 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+}
+
+/*
+Generated from the following glsl:
+
+#version 430
+layout(location=0) out float x;
+layout(location=1) out float y;
+
+void main(void) {
+ float accumulator_1 = 0;
+ float accumulator_2 = 0;
+ for (int i = 0; i < 10; i++) {
+ accumulator_1 += i;
+ accumulator_2 += i;
+ }
+
+ x = accumulator_1;
+ y = accumulator_2;
+}
+
+Should be split into equivalent of:
+
+void main(void) {
+ float accumulator_1 = 0;
+ float accumulator_2 = 0;
+ for (int i = 0; i < 10; i++) {
+ accumulator_1 += i;
+ }
+
+ for (int i = 0; i < 10; i++) {
+ accumulator_2 += i;
+ }
+ x = accumulator_1;
+ y = accumulator_2;
+}
+
+*/
+TEST_F(FissionClassTest, FissionWithPhisUsedOutwithLoop) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string source = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3 %4
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %5 "accumulator_1"
+ OpName %6 "accumulator_2"
+ OpName %7 "i"
+ OpName %3 "x"
+ OpName %4 "y"
+ OpDecorate %3 Location 0
+ OpDecorate %4 Location 1
+ %8 = OpTypeVoid
+ %9 = OpTypeFunction %8
+ %10 = OpTypeFloat 32
+ %11 = OpTypePointer Function %10
+ %12 = OpConstant %10 0
+ %13 = OpTypeInt 32 1
+ %14 = OpTypePointer Function %13
+ %15 = OpConstant %13 0
+ %16 = OpConstant %13 10
+ %17 = OpTypeBool
+ %18 = OpConstant %13 1
+ %19 = OpTypePointer Output %10
+ %3 = OpVariable %19 Output
+ %4 = OpVariable %19 Output
+ %2 = OpFunction %8 None %9
+ %20 = OpLabel
+ %5 = OpVariable %11 Function
+ %6 = OpVariable %11 Function
+ %7 = OpVariable %14 Function
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpPhi %10 %12 %20 %23 %24
+ %25 = OpPhi %10 %12 %20 %26 %24
+ %27 = OpPhi %13 %15 %20 %28 %24
+ OpLoopMerge %29 %24 None
+ OpBranch %30
+ %30 = OpLabel
+ %31 = OpSLessThan %17 %27 %16
+ OpBranchConditional %31 %32 %29
+ %32 = OpLabel
+ %33 = OpConvertSToF %10 %27
+ %26 = OpFAdd %10 %25 %33
+ %34 = OpConvertSToF %10 %27
+ %23 = OpFAdd %10 %22 %34
+ OpBranch %24
+ %24 = OpLabel
+ %28 = OpIAdd %13 %27 %18
+ OpStore %7 %28
+ OpBranch %21
+ %29 = OpLabel
+ OpStore %3 %25
+ OpStore %4 %22
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3 %4
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %5 "accumulator_1"
+OpName %6 "accumulator_2"
+OpName %7 "i"
+OpName %3 "x"
+OpName %4 "y"
+OpDecorate %3 Location 0
+OpDecorate %4 Location 1
+%8 = OpTypeVoid
+%9 = OpTypeFunction %8
+%10 = OpTypeFloat 32
+%11 = OpTypePointer Function %10
+%12 = OpConstant %10 0
+%13 = OpTypeInt 32 1
+%14 = OpTypePointer Function %13
+%15 = OpConstant %13 0
+%16 = OpConstant %13 10
+%17 = OpTypeBool
+%18 = OpConstant %13 1
+%19 = OpTypePointer Output %10
+%3 = OpVariable %19 Output
+%4 = OpVariable %19 Output
+%2 = OpFunction %8 None %9
+%20 = OpLabel
+%5 = OpVariable %11 Function
+%6 = OpVariable %11 Function
+%7 = OpVariable %14 Function
+OpBranch %35
+%35 = OpLabel
+%37 = OpPhi %10 %12 %20 %43 %46
+%38 = OpPhi %13 %15 %20 %47 %46
+OpLoopMerge %48 %46 None
+OpBranch %39
+%39 = OpLabel
+%40 = OpSLessThan %17 %38 %16
+OpBranchConditional %40 %41 %48
+%41 = OpLabel
+%42 = OpConvertSToF %10 %38
+%43 = OpFAdd %10 %37 %42
+OpBranch %46
+%46 = OpLabel
+%47 = OpIAdd %13 %38 %18
+OpStore %7 %47
+OpBranch %35
+%48 = OpLabel
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %10 %12 %48 %23 %24
+%27 = OpPhi %13 %15 %48 %28 %24
+OpLoopMerge %29 %24 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpSLessThan %17 %27 %16
+OpBranchConditional %31 %32 %29
+%32 = OpLabel
+%34 = OpConvertSToF %10 %27
+%23 = OpFAdd %10 %22 %34
+OpBranch %24
+%24 = OpLabel
+%28 = OpIAdd %13 %27 %18
+OpStore %7 %28
+OpBranch %21
+%29 = OpLabel
+OpStore %3 %37
+OpStore %4 %22
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10][10];
+ float B[10][10];
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ A[i][j] = B[i][j];
+ B[i][j] = A[i][j];
+ }
+ }
+}
+
+Should be split into equivalent of:
+
+#version 430
+void main(void) {
+ float A[10][10];
+ float B[10][10];
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ A[i][j] = B[i][j];
+ }
+ for (int j = 0; j < 10; j++) {
+ B[i][j] = A[i][j];
+ }
+ }
+}
+
+
+*/
+TEST_F(FissionClassTest, FissionNested) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string source = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "j"
+ OpName %5 "A"
+ OpName %6 "B"
+ %7 = OpTypeVoid
+ %8 = OpTypeFunction %7
+ %9 = OpTypeInt 32 1
+ %10 = OpTypePointer Function %9
+ %11 = OpConstant %9 0
+ %12 = OpConstant %9 10
+ %13 = OpTypeBool
+ %14 = OpTypeFloat 32
+ %15 = OpTypeInt 32 0
+ %16 = OpConstant %15 10
+ %17 = OpTypeArray %14 %16
+ %18 = OpTypeArray %17 %16
+ %19 = OpTypePointer Function %18
+ %20 = OpTypePointer Function %14
+ %21 = OpConstant %9 1
+ %2 = OpFunction %7 None %8
+ %22 = OpLabel
+ %3 = OpVariable %10 Function
+ %4 = OpVariable %10 Function
+ %5 = OpVariable %19 Function
+ %6 = OpVariable %19 Function
+ OpStore %3 %11
+ OpBranch %23
+ %23 = OpLabel
+ %24 = OpPhi %9 %11 %22 %25 %26
+ OpLoopMerge %27 %26 None
+ OpBranch %28
+ %28 = OpLabel
+ %29 = OpSLessThan %13 %24 %12
+ OpBranchConditional %29 %30 %27
+ %30 = OpLabel
+ OpStore %4 %11
+ OpBranch %31
+ %31 = OpLabel
+ %32 = OpPhi %9 %11 %30 %33 %34
+ OpLoopMerge %35 %34 None
+ OpBranch %36
+ %36 = OpLabel
+ %37 = OpSLessThan %13 %32 %12
+ OpBranchConditional %37 %38 %35
+ %38 = OpLabel
+ %39 = OpAccessChain %20 %6 %24 %32
+ %40 = OpLoad %14 %39
+ %41 = OpAccessChain %20 %5 %24 %32
+ OpStore %41 %40
+ %42 = OpAccessChain %20 %5 %24 %32
+ %43 = OpLoad %14 %42
+ %44 = OpAccessChain %20 %6 %24 %32
+ OpStore %44 %43
+ OpBranch %34
+ %34 = OpLabel
+ %33 = OpIAdd %9 %32 %21
+ OpStore %4 %33
+ OpBranch %31
+ %35 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %25 = OpIAdd %9 %24 %21
+ OpStore %3 %25
+ OpBranch %23
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "j"
+OpName %5 "A"
+OpName %6 "B"
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpTypeFloat 32
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 10
+%17 = OpTypeArray %14 %16
+%18 = OpTypeArray %17 %16
+%19 = OpTypePointer Function %18
+%20 = OpTypePointer Function %14
+%21 = OpConstant %9 1
+%2 = OpFunction %7 None %8
+%22 = OpLabel
+%3 = OpVariable %10 Function
+%4 = OpVariable %10 Function
+%5 = OpVariable %19 Function
+%6 = OpVariable %19 Function
+OpStore %3 %11
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %9 %11 %22 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %13 %24 %12
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+OpStore %4 %11
+OpBranch %45
+%45 = OpLabel
+%46 = OpPhi %9 %11 %30 %57 %56
+OpLoopMerge %58 %56 None
+OpBranch %47
+%47 = OpLabel
+%48 = OpSLessThan %13 %46 %12
+OpBranchConditional %48 %49 %58
+%49 = OpLabel
+%50 = OpAccessChain %20 %6 %24 %46
+%51 = OpLoad %14 %50
+%52 = OpAccessChain %20 %5 %24 %46
+OpStore %52 %51
+OpBranch %56
+%56 = OpLabel
+%57 = OpIAdd %9 %46 %21
+OpStore %4 %57
+OpBranch %45
+%58 = OpLabel
+OpBranch %31
+%31 = OpLabel
+%32 = OpPhi %9 %11 %58 %33 %34
+OpLoopMerge %35 %34 None
+OpBranch %36
+%36 = OpLabel
+%37 = OpSLessThan %13 %32 %12
+OpBranchConditional %37 %38 %35
+%38 = OpLabel
+%42 = OpAccessChain %20 %5 %24 %32
+%43 = OpLoad %14 %42
+%44 = OpAccessChain %20 %6 %24 %32
+OpStore %44 %43
+OpBranch %34
+%34 = OpLabel
+%33 = OpIAdd %9 %32 %21
+OpStore %4 %33
+OpBranch %31
+%35 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %9 %24 %21
+OpStore %3 %25
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+}
+
+/*
+#version 430
+void main(void) {
+ int accumulator = 0;
+ float A[10];
+ float B[10];
+ float C[10];
+
+ for (int i = 0; i < 10; i++) {
+ int c = C[i];
+ A[i] = B[i];
+ B[i] = A[i] + c;
+ }
+}
+
+This loop should not be split as we would have to break the order of the loads
+to do so. It would be grouped into two sets:
+
+1
+ int c = C[i];
+ B[i] = A[i] + c;
+
+2
+ A[i] = B[i];
+
+To keep the load C[i] in the same order we would need to put B[i] ahead of that
+*/
+TEST_F(FissionClassTest, FissionLoad) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "c"
+OpName %5 "C"
+OpName %6 "A"
+OpName %7 "B"
+%8 = OpTypeVoid
+%9 = OpTypeFunction %8
+%10 = OpTypeInt 32 1
+%11 = OpTypePointer Function %10
+%12 = OpConstant %10 0
+%13 = OpConstant %10 10
+%14 = OpTypeBool
+%15 = OpTypeFloat 32
+%16 = OpTypePointer Function %15
+%17 = OpTypeInt 32 0
+%18 = OpConstant %17 10
+%19 = OpTypeArray %15 %18
+%20 = OpTypePointer Function %19
+%21 = OpConstant %10 1
+%2 = OpFunction %8 None %9
+%22 = OpLabel
+%3 = OpVariable %11 Function
+%4 = OpVariable %16 Function
+%5 = OpVariable %20 Function
+%6 = OpVariable %20 Function
+%7 = OpVariable %20 Function
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %10 %12 %22 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %14 %24 %13
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+%31 = OpAccessChain %16 %5 %24
+%32 = OpLoad %15 %31
+OpStore %4 %32
+%33 = OpAccessChain %16 %7 %24
+%34 = OpLoad %15 %33
+%35 = OpAccessChain %16 %6 %24
+OpStore %35 %34
+%36 = OpAccessChain %16 %6 %24
+%37 = OpLoad %15 %36
+%38 = OpFAdd %15 %37 %32
+%39 = OpAccessChain %16 %7 %24
+OpStore %39 %38
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %10 %24 %21
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+#version 430
+layout(location=0) flat in int condition;
+void main(void) {
+ float A[10];
+ float B[10];
+
+ for (int i = 0; i < 10; i++) {
+ if (condition == 1)
+ A[i] = B[i];
+ else
+ B[i] = A[i];
+ }
+}
+
+
+When this is split we leave the condition check and control flow inplace and
+leave its removal for dead code elimination.
+
+#version 430
+layout(location=0) flat in int condition;
+void main(void) {
+ float A[10];
+ float B[10];
+
+ for (int i = 0; i < 10; i++) {
+ if (condition == 1)
+ A[i] = B[i];
+ else
+ ;
+ }
+ for (int i = 0; i < 10; i++) {
+ if (condition == 1)
+ ;
+ else
+ B[i] = A[i];
+ }
+}
+
+
+*/
+TEST_F(FissionClassTest, FissionControlFlow) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string source = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %4 "i"
+ OpName %3 "condition"
+ OpName %5 "A"
+ OpName %6 "B"
+ OpDecorate %3 Flat
+ OpDecorate %3 Location 0
+ %7 = OpTypeVoid
+ %8 = OpTypeFunction %7
+ %9 = OpTypeInt 32 1
+ %10 = OpTypePointer Function %9
+ %11 = OpConstant %9 0
+ %12 = OpConstant %9 10
+ %13 = OpTypeBool
+ %14 = OpTypePointer Input %9
+ %3 = OpVariable %14 Input
+ %15 = OpConstant %9 1
+ %16 = OpTypeFloat 32
+ %17 = OpTypeInt 32 0
+ %18 = OpConstant %17 10
+ %19 = OpTypeArray %16 %18
+ %20 = OpTypePointer Function %19
+ %21 = OpTypePointer Function %16
+ %2 = OpFunction %7 None %8
+ %22 = OpLabel
+ %4 = OpVariable %10 Function
+ %5 = OpVariable %20 Function
+ %6 = OpVariable %20 Function
+ %31 = OpLoad %9 %3
+ OpStore %4 %11
+ OpBranch %23
+ %23 = OpLabel
+ %24 = OpPhi %9 %11 %22 %25 %26
+ OpLoopMerge %27 %26 None
+ OpBranch %28
+ %28 = OpLabel
+ %29 = OpSLessThan %13 %24 %12
+ OpBranchConditional %29 %30 %27
+ %30 = OpLabel
+ %32 = OpIEqual %13 %31 %15
+ OpSelectionMerge %33 None
+ OpBranchConditional %32 %34 %35
+ %34 = OpLabel
+ %36 = OpAccessChain %21 %6 %24
+ %37 = OpLoad %16 %36
+ %38 = OpAccessChain %21 %5 %24
+ OpStore %38 %37
+ OpBranch %33
+ %35 = OpLabel
+ %39 = OpAccessChain %21 %5 %24
+ %40 = OpLoad %16 %39
+ %41 = OpAccessChain %21 %6 %24
+ OpStore %41 %40
+ OpBranch %33
+ %33 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %25 = OpIAdd %9 %24 %15
+ OpStore %4 %25
+ OpBranch %23
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %4 "i"
+OpName %3 "condition"
+OpName %5 "A"
+OpName %6 "B"
+OpDecorate %3 Flat
+OpDecorate %3 Location 0
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpTypePointer Input %9
+%3 = OpVariable %14 Input
+%15 = OpConstant %9 1
+%16 = OpTypeFloat 32
+%17 = OpTypeInt 32 0
+%18 = OpConstant %17 10
+%19 = OpTypeArray %16 %18
+%20 = OpTypePointer Function %19
+%21 = OpTypePointer Function %16
+%2 = OpFunction %7 None %8
+%22 = OpLabel
+%4 = OpVariable %10 Function
+%5 = OpVariable %20 Function
+%6 = OpVariable %20 Function
+%23 = OpLoad %9 %3
+OpStore %4 %11
+OpBranch %42
+%42 = OpLabel
+%43 = OpPhi %9 %11 %22 %58 %57
+OpLoopMerge %59 %57 None
+OpBranch %44
+%44 = OpLabel
+%45 = OpSLessThan %13 %43 %12
+OpBranchConditional %45 %46 %59
+%46 = OpLabel
+%47 = OpIEqual %13 %23 %15
+OpSelectionMerge %56 None
+OpBranchConditional %47 %52 %48
+%48 = OpLabel
+OpBranch %56
+%52 = OpLabel
+%53 = OpAccessChain %21 %6 %43
+%54 = OpLoad %16 %53
+%55 = OpAccessChain %21 %5 %43
+OpStore %55 %54
+OpBranch %56
+%56 = OpLabel
+OpBranch %57
+%57 = OpLabel
+%58 = OpIAdd %9 %43 %15
+OpStore %4 %58
+OpBranch %42
+%59 = OpLabel
+OpBranch %24
+%24 = OpLabel
+%25 = OpPhi %9 %11 %59 %26 %27
+OpLoopMerge %28 %27 None
+OpBranch %29
+%29 = OpLabel
+%30 = OpSLessThan %13 %25 %12
+OpBranchConditional %30 %31 %28
+%31 = OpLabel
+%32 = OpIEqual %13 %23 %15
+OpSelectionMerge %33 None
+OpBranchConditional %32 %34 %35
+%34 = OpLabel
+OpBranch %33
+%35 = OpLabel
+%39 = OpAccessChain %21 %5 %25
+%40 = OpLoad %16 %39
+%41 = OpAccessChain %21 %6 %25
+OpStore %41 %40
+OpBranch %33
+%33 = OpLabel
+OpBranch %27
+%27 = OpLabel
+%26 = OpIAdd %9 %25 %15
+OpStore %4 %26
+OpBranch %24
+%28 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ if (i == 1)
+ B[i] = A[i];
+ else if (i == 2)
+ A[i] = B[i];
+ else
+ A[i] = 0;
+ }
+}
+
+After running the pass with multiple splits enabled (via register threshold of
+1) we expect the equivalent of:
+
+#version 430
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ if (i == 1)
+ B[i] = A[i];
+ else if (i == 2)
+ else
+ }
+ for (int i = 0; i < 10; i++) {
+ if (i == 1)
+ else if (i == 2)
+ A[i] = B[i];
+ else
+ }
+ for (int i = 0; i < 10; i++) {
+ if (i == 1)
+ else if (i == 2)
+ else
+ A[i] = 0;
+ }
+
+}
+
+*/
+TEST_F(FissionClassTest, FissionControlFlow2) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string source = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "B"
+ OpName %5 "A"
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpConstant %8 1
+ %14 = OpTypeFloat 32
+ %15 = OpTypeInt 32 0
+ %16 = OpConstant %15 10
+ %17 = OpTypeArray %14 %16
+ %18 = OpTypePointer Function %17
+ %19 = OpTypePointer Function %14
+ %20 = OpConstant %8 2
+ %21 = OpConstant %14 0
+ %2 = OpFunction %6 None %7
+ %22 = OpLabel
+ %3 = OpVariable %9 Function
+ %4 = OpVariable %18 Function
+ %5 = OpVariable %18 Function
+ OpStore %3 %10
+ OpBranch %23
+ %23 = OpLabel
+ %24 = OpPhi %8 %10 %22 %25 %26
+ OpLoopMerge %27 %26 None
+ OpBranch %28
+ %28 = OpLabel
+ %29 = OpSLessThan %12 %24 %11
+ OpBranchConditional %29 %30 %27
+ %30 = OpLabel
+ %31 = OpIEqual %12 %24 %13
+ OpSelectionMerge %32 None
+ OpBranchConditional %31 %33 %34
+ %33 = OpLabel
+ %35 = OpAccessChain %19 %5 %24
+ %36 = OpLoad %14 %35
+ %37 = OpAccessChain %19 %4 %24
+ OpStore %37 %36
+ OpBranch %32
+ %34 = OpLabel
+ %38 = OpIEqual %12 %24 %20
+ OpSelectionMerge %39 None
+ OpBranchConditional %38 %40 %41
+ %40 = OpLabel
+ %42 = OpAccessChain %19 %4 %24
+ %43 = OpLoad %14 %42
+ %44 = OpAccessChain %19 %5 %24
+ OpStore %44 %43
+ OpBranch %39
+ %41 = OpLabel
+ %45 = OpAccessChain %19 %5 %24
+ OpStore %45 %21
+ OpBranch %39
+ %39 = OpLabel
+ OpBranch %32
+ %32 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %25 = OpIAdd %8 %24 %13
+ OpStore %3 %25
+ OpBranch %23
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpConstant %8 1
+%14 = OpTypeFloat 32
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 10
+%17 = OpTypeArray %14 %16
+%18 = OpTypePointer Function %17
+%19 = OpTypePointer Function %14
+%20 = OpConstant %8 2
+%21 = OpConstant %14 0
+%2 = OpFunction %6 None %7
+%22 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %18 Function
+%5 = OpVariable %18 Function
+OpStore %3 %10
+OpBranch %46
+%46 = OpLabel
+%47 = OpPhi %8 %10 %22 %67 %66
+OpLoopMerge %68 %66 None
+OpBranch %48
+%48 = OpLabel
+%49 = OpSLessThan %12 %47 %11
+OpBranchConditional %49 %50 %68
+%50 = OpLabel
+%51 = OpIEqual %12 %47 %13
+OpSelectionMerge %65 None
+OpBranchConditional %51 %61 %52
+%52 = OpLabel
+%53 = OpIEqual %12 %47 %20
+OpSelectionMerge %60 None
+OpBranchConditional %53 %56 %54
+%54 = OpLabel
+OpBranch %60
+%56 = OpLabel
+OpBranch %60
+%60 = OpLabel
+OpBranch %65
+%61 = OpLabel
+%62 = OpAccessChain %19 %5 %47
+%63 = OpLoad %14 %62
+%64 = OpAccessChain %19 %4 %47
+OpStore %64 %63
+OpBranch %65
+%65 = OpLabel
+OpBranch %66
+%66 = OpLabel
+%67 = OpIAdd %8 %47 %13
+OpStore %3 %67
+OpBranch %46
+%68 = OpLabel
+OpBranch %69
+%69 = OpLabel
+%70 = OpPhi %8 %10 %68 %87 %86
+OpLoopMerge %88 %86 None
+OpBranch %71
+%71 = OpLabel
+%72 = OpSLessThan %12 %70 %11
+OpBranchConditional %72 %73 %88
+%73 = OpLabel
+%74 = OpIEqual %12 %70 %13
+OpSelectionMerge %85 None
+OpBranchConditional %74 %84 %75
+%75 = OpLabel
+%76 = OpIEqual %12 %70 %20
+OpSelectionMerge %83 None
+OpBranchConditional %76 %79 %77
+%77 = OpLabel
+OpBranch %83
+%79 = OpLabel
+%80 = OpAccessChain %19 %4 %70
+%81 = OpLoad %14 %80
+%82 = OpAccessChain %19 %5 %70
+OpStore %82 %81
+OpBranch %83
+%83 = OpLabel
+OpBranch %85
+%84 = OpLabel
+OpBranch %85
+%85 = OpLabel
+OpBranch %86
+%86 = OpLabel
+%87 = OpIAdd %8 %70 %13
+OpStore %3 %87
+OpBranch %69
+%88 = OpLabel
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %8 %10 %88 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %12 %24 %11
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+%31 = OpIEqual %12 %24 %13
+OpSelectionMerge %32 None
+OpBranchConditional %31 %33 %34
+%33 = OpLabel
+OpBranch %32
+%34 = OpLabel
+%38 = OpIEqual %12 %24 %20
+OpSelectionMerge %39 None
+OpBranchConditional %38 %40 %41
+%40 = OpLabel
+OpBranch %39
+%41 = OpLabel
+%45 = OpAccessChain %19 %5 %24
+OpStore %45 %21
+OpBranch %39
+%39 = OpLabel
+OpBranch %32
+%32 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %8 %24 %13
+OpStore %3 %25
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true, 1);
+}
+
+/*
+#version 430
+layout(location=0) flat in int condition;
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ B[i] = A[i];
+ memoryBarrier();
+ A[i] = B[i];
+ }
+}
+
+This should not be split due to the memory barrier.
+*/
+TEST_F(FissionClassTest, FissionBarrier) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %4 "i"
+OpName %5 "B"
+OpName %6 "A"
+OpName %3 "condition"
+OpDecorate %3 Flat
+OpDecorate %3 Location 0
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpTypeFloat 32
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 10
+%17 = OpTypeArray %14 %16
+%18 = OpTypePointer Function %17
+%19 = OpTypePointer Function %14
+%20 = OpConstant %15 1
+%21 = OpConstant %15 4048
+%22 = OpConstant %9 1
+%23 = OpTypePointer Input %9
+%3 = OpVariable %23 Input
+%2 = OpFunction %7 None %8
+%24 = OpLabel
+%4 = OpVariable %10 Function
+%5 = OpVariable %18 Function
+%6 = OpVariable %18 Function
+OpStore %4 %11
+OpBranch %25
+%25 = OpLabel
+%26 = OpPhi %9 %11 %24 %27 %28
+OpLoopMerge %29 %28 None
+OpBranch %30
+%30 = OpLabel
+%31 = OpSLessThan %13 %26 %12
+OpBranchConditional %31 %32 %29
+%32 = OpLabel
+%33 = OpAccessChain %19 %6 %26
+%34 = OpLoad %14 %33
+%35 = OpAccessChain %19 %5 %26
+OpStore %35 %34
+OpMemoryBarrier %20 %21
+%36 = OpAccessChain %19 %5 %26
+%37 = OpLoad %14 %36
+%38 = OpAccessChain %19 %6 %26
+OpStore %38 %37
+OpBranch %28
+%28 = OpLabel
+%27 = OpIAdd %9 %26 %22
+OpStore %4 %27
+OpBranch %25
+%29 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ B[i] = A[i];
+ if ( i== 1)
+ break;
+ A[i] = B[i];
+ }
+}
+
+This should not be split due to the break.
+*/
+TEST_F(FissionClassTest, FissionBreak) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpStore %3 %10
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %20 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%29 = OpAccessChain %18 %5 %22
+%30 = OpLoad %13 %29
+%31 = OpAccessChain %18 %4 %22
+OpStore %31 %30
+%32 = OpIEqual %12 %22 %19
+OpSelectionMerge %33 None
+OpBranchConditional %32 %34 %33
+%34 = OpLabel
+OpBranch %25
+%33 = OpLabel
+%35 = OpAccessChain %18 %4 %22
+%36 = OpLoad %13 %35
+%37 = OpAccessChain %18 %5 %22
+OpStore %37 %36
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpStore %3 %23
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; i++) {
+ B[i] = A[i];
+ if ( i== 1)
+ continue;
+ A[i] = B[i];
+ }
+}
+
+This loop should be split into:
+
+ for (int i = 0; i < 10; i++) {
+ B[i] = A[i];
+ if ( i== 1)
+ continue;
+ }
+ for (int i = 0; i < 10; i++) {
+ if ( i== 1)
+ continue;
+ A[i] = B[i];
+ }
+The continue block in the first loop is left to DCE.
+}
+
+
+*/
+TEST_F(FissionClassTest, FissionContinue) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpStore %3 %10
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %20 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%29 = OpAccessChain %18 %5 %22
+%30 = OpLoad %13 %29
+%31 = OpAccessChain %18 %4 %22
+OpStore %31 %30
+%32 = OpIEqual %12 %22 %19
+OpSelectionMerge %33 None
+OpBranchConditional %32 %34 %33
+%34 = OpLabel
+OpBranch %24
+%33 = OpLabel
+%35 = OpAccessChain %18 %4 %22
+%36 = OpLoad %13 %35
+%37 = OpAccessChain %18 %5 %22
+OpStore %37 %36
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpStore %3 %23
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpStore %3 %10
+OpBranch %38
+%38 = OpLabel
+%39 = OpPhi %8 %10 %20 %53 %52
+OpLoopMerge %54 %52 None
+OpBranch %40
+%40 = OpLabel
+%41 = OpSLessThan %12 %39 %11
+OpBranchConditional %41 %42 %54
+%42 = OpLabel
+%43 = OpAccessChain %18 %5 %39
+%44 = OpLoad %13 %43
+%45 = OpAccessChain %18 %4 %39
+OpStore %45 %44
+%46 = OpIEqual %12 %39 %19
+OpSelectionMerge %47 None
+OpBranchConditional %46 %51 %47
+%47 = OpLabel
+OpBranch %52
+%51 = OpLabel
+OpBranch %52
+%52 = OpLabel
+%53 = OpIAdd %8 %39 %19
+OpStore %3 %53
+OpBranch %38
+%54 = OpLabel
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %54 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%32 = OpIEqual %12 %22 %19
+OpSelectionMerge %33 None
+OpBranchConditional %32 %34 %33
+%34 = OpLabel
+OpBranch %24
+%33 = OpLabel
+%35 = OpAccessChain %18 %4 %22
+%36 = OpLoad %13 %35
+%37 = OpAccessChain %18 %5 %22
+OpStore %37 %36
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpStore %3 %23
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10];
+ float B[10];
+ int i = 0;
+ do {
+ B[i] = A[i];
+ A[i] = B[i];
+ ++i;
+ } while (i < 10);
+}
+
+
+Check that this is split into:
+ int i = 0;
+ do {
+ B[i] = A[i];
+ ++i;
+ } while (i < 10);
+
+ i = 0;
+ do {
+ A[i] = B[i];
+ ++i;
+ } while (i < 10);
+
+
+*/
+TEST_F(FissionClassTest, FissionDoWhile) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpTypeFloat 32
+%12 = OpTypeInt 32 0
+%13 = OpConstant %12 10
+%14 = OpTypeArray %11 %13
+%15 = OpTypePointer Function %14
+%16 = OpTypePointer Function %11
+%17 = OpConstant %8 1
+%18 = OpConstant %8 10
+%19 = OpTypeBool
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %15 Function
+%5 = OpVariable %15 Function
+OpStore %3 %10
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %20 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpAccessChain %16 %5 %22
+%28 = OpLoad %11 %27
+%29 = OpAccessChain %16 %4 %22
+OpStore %29 %28
+%30 = OpAccessChain %16 %4 %22
+%31 = OpLoad %11 %30
+%32 = OpAccessChain %16 %5 %22
+OpStore %32 %31
+%23 = OpIAdd %8 %22 %17
+OpStore %3 %23
+OpBranch %24
+%24 = OpLabel
+%33 = OpSLessThan %19 %23 %18
+OpBranchConditional %33 %21 %25
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpTypeFloat 32
+%12 = OpTypeInt 32 0
+%13 = OpConstant %12 10
+%14 = OpTypeArray %11 %13
+%15 = OpTypePointer Function %14
+%16 = OpTypePointer Function %11
+%17 = OpConstant %8 1
+%18 = OpConstant %8 10
+%19 = OpTypeBool
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %15 Function
+%5 = OpVariable %15 Function
+OpStore %3 %10
+OpBranch %34
+%34 = OpLabel
+%35 = OpPhi %8 %10 %20 %43 %44
+OpLoopMerge %46 %44 None
+OpBranch %36
+%36 = OpLabel
+%37 = OpAccessChain %16 %5 %35
+%38 = OpLoad %11 %37
+%39 = OpAccessChain %16 %4 %35
+OpStore %39 %38
+%43 = OpIAdd %8 %35 %17
+OpStore %3 %43
+OpBranch %44
+%44 = OpLabel
+%45 = OpSLessThan %19 %43 %18
+OpBranchConditional %45 %34 %46
+%46 = OpLabel
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %46 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%30 = OpAccessChain %16 %4 %22
+%31 = OpLoad %11 %30
+%32 = OpAccessChain %16 %5 %22
+OpStore %32 %31
+%23 = OpIAdd %8 %22 %17
+OpStore %3 %23
+OpBranch %24
+%24 = OpLabel
+%33 = OpSLessThan %19 %23 %18
+OpBranchConditional %33 %21 %25
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+}
+
+/*
+
+#version 430
+void main(void) {
+ float A[10][10];
+ float B[10][10];
+ for (int j = 0; j < 10; ++j) {
+ for (int i = 0; i < 10; ++i) {
+ B[i][j] = A[i][i];
+ A[i][i] = B[i][j + 1];
+ }
+ }
+}
+
+
+This loop can't be split because the load B[i][j + 1] is dependent on the store
+B[i][j].
+
+*/
+TEST_F(FissionClassTest, FissionNestedDependency) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "j"
+OpName %4 "i"
+OpName %5 "B"
+OpName %6 "A"
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpTypeFloat 32
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 10
+%17 = OpTypeArray %14 %16
+%18 = OpTypeArray %17 %16
+%19 = OpTypePointer Function %18
+%20 = OpTypePointer Function %14
+%21 = OpConstant %9 1
+%2 = OpFunction %7 None %8
+%22 = OpLabel
+%3 = OpVariable %10 Function
+%4 = OpVariable %10 Function
+%5 = OpVariable %19 Function
+%6 = OpVariable %19 Function
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %9 %11 %22 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %13 %24 %12
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+OpBranch %31
+%31 = OpLabel
+%32 = OpPhi %9 %11 %30 %33 %34
+OpLoopMerge %35 %34 None
+OpBranch %36
+%36 = OpLabel
+%37 = OpSLessThan %13 %32 %12
+OpBranchConditional %37 %38 %35
+%38 = OpLabel
+%39 = OpAccessChain %20 %6 %32 %32
+%40 = OpLoad %14 %39
+%41 = OpAccessChain %20 %5 %32 %24
+OpStore %41 %40
+%42 = OpIAdd %9 %24 %21
+%43 = OpAccessChain %20 %5 %32 %42
+%44 = OpLoad %14 %43
+%45 = OpAccessChain %20 %6 %32 %32
+OpStore %45 %44
+OpBranch %34
+%34 = OpLabel
+%33 = OpIAdd %9 %32 %21
+OpBranch %31
+%35 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %9 %24 %21
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10][10];
+ float B[10][10];
+ for (int j = 0; j < 10; ++j) {
+ for (int i = 0; i < 10; ++i) {
+ B[i][i] = A[i][j];
+ A[i][j+1] = B[i][i];
+ }
+ }
+}
+
+This loop should not be split as the load A[i][j+1] would be reading a value
+written in the store A[i][j] which would be hit before A[i][j+1] if the loops
+where split but would not get hit before the read currently.
+
+*/
+TEST_F(FissionClassTest, FissionNestedDependency2) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "j"
+OpName %4 "i"
+OpName %5 "B"
+OpName %6 "A"
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpTypeFloat 32
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 10
+%17 = OpTypeArray %14 %16
+%18 = OpTypeArray %17 %16
+%19 = OpTypePointer Function %18
+%20 = OpTypePointer Function %14
+%21 = OpConstant %9 1
+%2 = OpFunction %7 None %8
+%22 = OpLabel
+%3 = OpVariable %10 Function
+%4 = OpVariable %10 Function
+%5 = OpVariable %19 Function
+%6 = OpVariable %19 Function
+OpStore %3 %11
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %9 %11 %22 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %13 %24 %12
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+OpStore %4 %11
+OpBranch %31
+%31 = OpLabel
+%32 = OpPhi %9 %11 %30 %33 %34
+OpLoopMerge %35 %34 None
+OpBranch %36
+%36 = OpLabel
+%37 = OpSLessThan %13 %32 %12
+OpBranchConditional %37 %38 %35
+%38 = OpLabel
+%39 = OpAccessChain %20 %6 %32 %24
+%40 = OpLoad %14 %39
+%41 = OpAccessChain %20 %5 %32 %32
+OpStore %41 %40
+%42 = OpIAdd %9 %24 %21
+%43 = OpAccessChain %20 %5 %32 %32
+%44 = OpLoad %14 %43
+%45 = OpAccessChain %20 %6 %32 %42
+OpStore %45 %44
+OpBranch %34
+%34 = OpLabel
+%33 = OpIAdd %9 %32 %21
+OpStore %4 %33
+OpBranch %31
+%35 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %9 %24 %21
+OpStore %3 %25
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10][10];
+ float B[10][10];
+ for (int j = 0; j < 10; ++j) {
+ for (int i = 0; i < 10; ++i) {
+ B[i][j] = A[i][j];
+ A[i][j] = B[i][j];
+ }
+ for (int i = 0; i < 10; ++i) {
+ B[i][j] = A[i][j];
+ A[i][j] = B[i][j];
+ }
+ }
+}
+
+
+
+Should be split into:
+
+for (int j = 0; j < 10; ++j) {
+ for (int i = 0; i < 10; ++i)
+ B[i][j] = A[i][j];
+ for (int i = 0; i < 10; ++i)
+ A[i][j] = B[i][j];
+ for (int i = 0; i < 10; ++i)
+ B[i][j] = A[i][j];
+ for (int i = 0; i < 10; ++i)
+ A[i][j] = B[i][j];
+*/
+TEST_F(FissionClassTest, FissionMultipleLoopsNested) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "j"
+ OpName %4 "i"
+ OpName %5 "B"
+ OpName %6 "A"
+ OpName %7 "i"
+ %8 = OpTypeVoid
+ %9 = OpTypeFunction %8
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %12 = OpConstant %10 0
+ %13 = OpConstant %10 10
+ %14 = OpTypeBool
+ %15 = OpTypeFloat 32
+ %16 = OpTypeInt 32 0
+ %17 = OpConstant %16 10
+ %18 = OpTypeArray %15 %17
+ %19 = OpTypeArray %18 %17
+ %20 = OpTypePointer Function %19
+ %21 = OpTypePointer Function %15
+ %22 = OpConstant %10 1
+ %2 = OpFunction %8 None %9
+ %23 = OpLabel
+ %3 = OpVariable %11 Function
+ %4 = OpVariable %11 Function
+ %5 = OpVariable %20 Function
+ %6 = OpVariable %20 Function
+ %7 = OpVariable %11 Function
+ OpStore %3 %12
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpPhi %10 %12 %23 %26 %27
+ OpLoopMerge %28 %27 None
+ OpBranch %29
+ %29 = OpLabel
+ %30 = OpSLessThan %14 %25 %13
+ OpBranchConditional %30 %31 %28
+ %31 = OpLabel
+ OpStore %4 %12
+ OpBranch %32
+ %32 = OpLabel
+ %33 = OpPhi %10 %12 %31 %34 %35
+ OpLoopMerge %36 %35 None
+ OpBranch %37
+ %37 = OpLabel
+ %38 = OpSLessThan %14 %33 %13
+ OpBranchConditional %38 %39 %36
+ %39 = OpLabel
+ %40 = OpAccessChain %21 %6 %33 %25
+ %41 = OpLoad %15 %40
+ %42 = OpAccessChain %21 %5 %33 %25
+ OpStore %42 %41
+ %43 = OpAccessChain %21 %5 %33 %25
+ %44 = OpLoad %15 %43
+ %45 = OpAccessChain %21 %6 %33 %25
+ OpStore %45 %44
+ OpBranch %35
+ %35 = OpLabel
+ %34 = OpIAdd %10 %33 %22
+ OpStore %4 %34
+ OpBranch %32
+ %36 = OpLabel
+ OpStore %7 %12
+ OpBranch %46
+ %46 = OpLabel
+ %47 = OpPhi %10 %12 %36 %48 %49
+ OpLoopMerge %50 %49 None
+ OpBranch %51
+ %51 = OpLabel
+ %52 = OpSLessThan %14 %47 %13
+ OpBranchConditional %52 %53 %50
+ %53 = OpLabel
+ %54 = OpAccessChain %21 %6 %47 %25
+ %55 = OpLoad %15 %54
+ %56 = OpAccessChain %21 %5 %47 %25
+ OpStore %56 %55
+ %57 = OpAccessChain %21 %5 %47 %25
+ %58 = OpLoad %15 %57
+ %59 = OpAccessChain %21 %6 %47 %25
+ OpStore %59 %58
+ OpBranch %49
+ %49 = OpLabel
+ %48 = OpIAdd %10 %47 %22
+ OpStore %7 %48
+ OpBranch %46
+ %50 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %26 = OpIAdd %10 %25 %22
+ OpStore %3 %26
+ OpBranch %24
+ %28 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "j"
+OpName %4 "i"
+OpName %5 "B"
+OpName %6 "A"
+OpName %7 "i"
+%8 = OpTypeVoid
+%9 = OpTypeFunction %8
+%10 = OpTypeInt 32 1
+%11 = OpTypePointer Function %10
+%12 = OpConstant %10 0
+%13 = OpConstant %10 10
+%14 = OpTypeBool
+%15 = OpTypeFloat 32
+%16 = OpTypeInt 32 0
+%17 = OpConstant %16 10
+%18 = OpTypeArray %15 %17
+%19 = OpTypeArray %18 %17
+%20 = OpTypePointer Function %19
+%21 = OpTypePointer Function %15
+%22 = OpConstant %10 1
+%2 = OpFunction %8 None %9
+%23 = OpLabel
+%3 = OpVariable %11 Function
+%4 = OpVariable %11 Function
+%5 = OpVariable %20 Function
+%6 = OpVariable %20 Function
+%7 = OpVariable %11 Function
+OpStore %3 %12
+OpBranch %24
+%24 = OpLabel
+%25 = OpPhi %10 %12 %23 %26 %27
+OpLoopMerge %28 %27 None
+OpBranch %29
+%29 = OpLabel
+%30 = OpSLessThan %14 %25 %13
+OpBranchConditional %30 %31 %28
+%31 = OpLabel
+OpStore %4 %12
+OpBranch %60
+%60 = OpLabel
+%61 = OpPhi %10 %12 %31 %72 %71
+OpLoopMerge %73 %71 None
+OpBranch %62
+%62 = OpLabel
+%63 = OpSLessThan %14 %61 %13
+OpBranchConditional %63 %64 %73
+%64 = OpLabel
+%65 = OpAccessChain %21 %6 %61 %25
+%66 = OpLoad %15 %65
+%67 = OpAccessChain %21 %5 %61 %25
+OpStore %67 %66
+OpBranch %71
+%71 = OpLabel
+%72 = OpIAdd %10 %61 %22
+OpStore %4 %72
+OpBranch %60
+%73 = OpLabel
+OpBranch %32
+%32 = OpLabel
+%33 = OpPhi %10 %12 %73 %34 %35
+OpLoopMerge %36 %35 None
+OpBranch %37
+%37 = OpLabel
+%38 = OpSLessThan %14 %33 %13
+OpBranchConditional %38 %39 %36
+%39 = OpLabel
+%43 = OpAccessChain %21 %5 %33 %25
+%44 = OpLoad %15 %43
+%45 = OpAccessChain %21 %6 %33 %25
+OpStore %45 %44
+OpBranch %35
+%35 = OpLabel
+%34 = OpIAdd %10 %33 %22
+OpStore %4 %34
+OpBranch %32
+%36 = OpLabel
+OpStore %7 %12
+OpBranch %74
+%74 = OpLabel
+%75 = OpPhi %10 %12 %36 %86 %85
+OpLoopMerge %87 %85 None
+OpBranch %76
+%76 = OpLabel
+%77 = OpSLessThan %14 %75 %13
+OpBranchConditional %77 %78 %87
+%78 = OpLabel
+%79 = OpAccessChain %21 %6 %75 %25
+%80 = OpLoad %15 %79
+%81 = OpAccessChain %21 %5 %75 %25
+OpStore %81 %80
+OpBranch %85
+%85 = OpLabel
+%86 = OpIAdd %10 %75 %22
+OpStore %7 %86
+OpBranch %74
+%87 = OpLabel
+OpBranch %46
+%46 = OpLabel
+%47 = OpPhi %10 %12 %87 %48 %49
+OpLoopMerge %50 %49 None
+OpBranch %51
+%51 = OpLabel
+%52 = OpSLessThan %14 %47 %13
+OpBranchConditional %52 %53 %50
+%53 = OpLabel
+%57 = OpAccessChain %21 %5 %47 %25
+%58 = OpLoad %15 %57
+%59 = OpAccessChain %21 %6 %47 %25
+OpStore %59 %58
+OpBranch %49
+%49 = OpLabel
+%48 = OpIAdd %10 %47 %22
+OpStore %7 %48
+OpBranch %46
+%50 = OpLabel
+OpBranch %27
+%27 = OpLabel
+%26 = OpIAdd %10 %25 %22
+OpStore %3 %26
+OpBranch %24
+%28 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+ const Function* function = spvtest::GetFunction(module, 2);
+ LoopDescriptor& pre_pass_descriptor = *context->GetLoopDescriptor(function);
+ EXPECT_EQ(pre_pass_descriptor.NumLoops(), 3u);
+ EXPECT_EQ(pre_pass_descriptor.pre_begin()->NumImmediateChildren(), 2u);
+
+ // Test that the pass transforms the ir into the expected output.
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+
+ // Test that the loop descriptor is correctly maintained and updated by the
+ // pass.
+ LoopFissionPass loop_fission;
+ loop_fission.SetContextForTesting(context.get());
+ loop_fission.Process();
+
+ function = spvtest::GetFunction(module, 2);
+ LoopDescriptor& post_pass_descriptor = *context->GetLoopDescriptor(function);
+ EXPECT_EQ(post_pass_descriptor.NumLoops(), 5u);
+ EXPECT_EQ(post_pass_descriptor.pre_begin()->NumImmediateChildren(), 4u);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10][10];
+ float B[10][10];
+ for (int i = 0; i < 10; ++i) {
+ B[i][i] = A[i][i];
+ A[i][i] = B[i][i];
+ }
+ for (int i = 0; i < 10; ++i) {
+ B[i][i] = A[i][i];
+ A[i][i] = B[i][i]
+ }
+}
+
+
+
+Should be split into:
+
+ for (int i = 0; i < 10; ++i)
+ B[i][i] = A[i][i];
+ for (int i = 0; i < 10; ++i)
+ A[i][i] = B[i][i];
+ for (int i = 0; i < 10; ++i)
+ B[i][i] = A[i][i];
+ for (int i = 0; i < 10; ++i)
+ A[i][i] = B[i][i];
+*/
+TEST_F(FissionClassTest, FissionMultipleLoops) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "B"
+ OpName %5 "A"
+ OpName %6 "i"
+ %7 = OpTypeVoid
+ %8 = OpTypeFunction %7
+ %9 = OpTypeInt 32 1
+ %10 = OpTypePointer Function %9
+ %11 = OpConstant %9 0
+ %12 = OpConstant %9 10
+ %13 = OpTypeBool
+ %14 = OpTypeFloat 32
+ %15 = OpTypeInt 32 0
+ %16 = OpConstant %15 10
+ %17 = OpTypeArray %14 %16
+ %18 = OpTypePointer Function %17
+ %19 = OpTypePointer Function %14
+ %20 = OpConstant %9 1
+ %2 = OpFunction %7 None %8
+ %21 = OpLabel
+ %3 = OpVariable %10 Function
+ %4 = OpVariable %18 Function
+ %5 = OpVariable %18 Function
+ %6 = OpVariable %10 Function
+ OpStore %3 %11
+ OpBranch %22
+ %22 = OpLabel
+ %23 = OpPhi %9 %11 %21 %24 %25
+ OpLoopMerge %26 %25 None
+ OpBranch %27
+ %27 = OpLabel
+ %28 = OpSLessThan %13 %23 %12
+ OpBranchConditional %28 %29 %26
+ %29 = OpLabel
+ %30 = OpAccessChain %19 %5 %23
+ %31 = OpLoad %14 %30
+ %32 = OpAccessChain %19 %4 %23
+ OpStore %32 %31
+ %33 = OpAccessChain %19 %4 %23
+ %34 = OpLoad %14 %33
+ %35 = OpAccessChain %19 %5 %23
+ OpStore %35 %34
+ OpBranch %25
+ %25 = OpLabel
+ %24 = OpIAdd %9 %23 %20
+ OpStore %3 %24
+ OpBranch %22
+ %26 = OpLabel
+ OpStore %6 %11
+ OpBranch %36
+ %36 = OpLabel
+ %37 = OpPhi %9 %11 %26 %38 %39
+ OpLoopMerge %40 %39 None
+ OpBranch %41
+ %41 = OpLabel
+ %42 = OpSLessThan %13 %37 %12
+ OpBranchConditional %42 %43 %40
+ %43 = OpLabel
+ %44 = OpAccessChain %19 %5 %37
+ %45 = OpLoad %14 %44
+ %46 = OpAccessChain %19 %4 %37
+ OpStore %46 %45
+ %47 = OpAccessChain %19 %4 %37
+ %48 = OpLoad %14 %47
+ %49 = OpAccessChain %19 %5 %37
+ OpStore %49 %48
+ OpBranch %39
+ %39 = OpLabel
+ %38 = OpIAdd %9 %37 %20
+ OpStore %6 %38
+ OpBranch %36
+ %40 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+OpName %6 "i"
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpTypeFloat 32
+%15 = OpTypeInt 32 0
+%16 = OpConstant %15 10
+%17 = OpTypeArray %14 %16
+%18 = OpTypePointer Function %17
+%19 = OpTypePointer Function %14
+%20 = OpConstant %9 1
+%2 = OpFunction %7 None %8
+%21 = OpLabel
+%3 = OpVariable %10 Function
+%4 = OpVariable %18 Function
+%5 = OpVariable %18 Function
+%6 = OpVariable %10 Function
+OpStore %3 %11
+OpBranch %64
+%64 = OpLabel
+%65 = OpPhi %9 %11 %21 %76 %75
+OpLoopMerge %77 %75 None
+OpBranch %66
+%66 = OpLabel
+%67 = OpSLessThan %13 %65 %12
+OpBranchConditional %67 %68 %77
+%68 = OpLabel
+%69 = OpAccessChain %19 %5 %65
+%70 = OpLoad %14 %69
+%71 = OpAccessChain %19 %4 %65
+OpStore %71 %70
+OpBranch %75
+%75 = OpLabel
+%76 = OpIAdd %9 %65 %20
+OpStore %3 %76
+OpBranch %64
+%77 = OpLabel
+OpBranch %22
+%22 = OpLabel
+%23 = OpPhi %9 %11 %77 %24 %25
+OpLoopMerge %26 %25 None
+OpBranch %27
+%27 = OpLabel
+%28 = OpSLessThan %13 %23 %12
+OpBranchConditional %28 %29 %26
+%29 = OpLabel
+%33 = OpAccessChain %19 %4 %23
+%34 = OpLoad %14 %33
+%35 = OpAccessChain %19 %5 %23
+OpStore %35 %34
+OpBranch %25
+%25 = OpLabel
+%24 = OpIAdd %9 %23 %20
+OpStore %3 %24
+OpBranch %22
+%26 = OpLabel
+OpStore %6 %11
+OpBranch %50
+%50 = OpLabel
+%51 = OpPhi %9 %11 %26 %62 %61
+OpLoopMerge %63 %61 None
+OpBranch %52
+%52 = OpLabel
+%53 = OpSLessThan %13 %51 %12
+OpBranchConditional %53 %54 %63
+%54 = OpLabel
+%55 = OpAccessChain %19 %5 %51
+%56 = OpLoad %14 %55
+%57 = OpAccessChain %19 %4 %51
+OpStore %57 %56
+OpBranch %61
+%61 = OpLabel
+%62 = OpIAdd %9 %51 %20
+OpStore %6 %62
+OpBranch %50
+%63 = OpLabel
+OpBranch %36
+%36 = OpLabel
+%37 = OpPhi %9 %11 %63 %38 %39
+OpLoopMerge %40 %39 None
+OpBranch %41
+%41 = OpLabel
+%42 = OpSLessThan %13 %37 %12
+OpBranchConditional %42 %43 %40
+%43 = OpLabel
+%47 = OpAccessChain %19 %4 %37
+%48 = OpLoad %14 %47
+%49 = OpAccessChain %19 %5 %37
+OpStore %49 %48
+OpBranch %39
+%39 = OpLabel
+%38 = OpIAdd %9 %37 %20
+OpStore %6 %38
+OpBranch %36
+%40 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+
+ const Function* function = spvtest::GetFunction(module, 2);
+ LoopDescriptor& pre_pass_descriptor = *context->GetLoopDescriptor(function);
+ EXPECT_EQ(pre_pass_descriptor.NumLoops(), 2u);
+ EXPECT_EQ(pre_pass_descriptor.pre_begin()->NumImmediateChildren(), 0u);
+
+ // Test that the pass transforms the ir into the expected output.
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+
+ // Test that the loop descriptor is correctly maintained and updated by the
+ // pass.
+ LoopFissionPass loop_fission;
+ loop_fission.SetContextForTesting(context.get());
+ loop_fission.Process();
+
+ function = spvtest::GetFunction(module, 2);
+ LoopDescriptor& post_pass_descriptor = *context->GetLoopDescriptor(function);
+ EXPECT_EQ(post_pass_descriptor.NumLoops(), 4u);
+ EXPECT_EQ(post_pass_descriptor.pre_begin()->NumImmediateChildren(), 0u);
+}
+
+/*
+#version 430
+int foo() { return 1; }
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; ++i) {
+ B[i] = A[i];
+ foo();
+ A[i] = B[i];
+ }
+}
+
+This should not be split as it has a function call in it so we can't determine
+if it has side effects.
+*/
+TEST_F(FissionClassTest, FissionFunctionCall) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "foo("
+OpName %4 "i"
+OpName %5 "B"
+OpName %6 "A"
+%7 = OpTypeVoid
+%8 = OpTypeFunction %7
+%9 = OpTypeInt 32 1
+%10 = OpTypeFunction %9
+%11 = OpConstant %9 1
+%12 = OpTypePointer Function %9
+%13 = OpConstant %9 0
+%14 = OpConstant %9 10
+%15 = OpTypeBool
+%16 = OpTypeFloat 32
+%17 = OpTypeInt 32 0
+%18 = OpConstant %17 10
+%19 = OpTypeArray %16 %18
+%20 = OpTypePointer Function %19
+%21 = OpTypePointer Function %16
+%2 = OpFunction %7 None %8
+%22 = OpLabel
+%4 = OpVariable %12 Function
+%5 = OpVariable %20 Function
+%6 = OpVariable %20 Function
+OpStore %4 %13
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %9 %13 %22 %25 %26
+OpLoopMerge %27 %26 None
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %15 %24 %14
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+%31 = OpAccessChain %21 %6 %24
+%32 = OpLoad %16 %31
+%33 = OpAccessChain %21 %5 %24
+OpStore %33 %32
+%34 = OpFunctionCall %9 %3
+%35 = OpAccessChain %21 %5 %24
+%36 = OpLoad %16 %35
+%37 = OpAccessChain %21 %6 %24
+OpStore %37 %36
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %9 %24 %11
+OpStore %4 %25
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+%3 = OpFunction %9 None %10
+%38 = OpLabel
+OpReturnValue %11
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, source, true);
+}
+
+/*
+#version 430
+void main(void) {
+ float A[10];
+ float B[10];
+ for (int i = 0; i < 10; ++i) {
+ switch (i) {
+ case 1:
+ B[i] = A[i];
+ break;
+ default:
+ A[i] = B[i];
+ }
+ }
+}
+
+This should be split into:
+ for (int i = 0; i < 10; ++i) {
+ switch (i) {
+ case 1:
+ break;
+ default:
+ A[i] = B[i];
+ }
+ }
+
+ for (int i = 0; i < 10; ++i) {
+ switch (i) {
+ case 1:
+ B[i] = A[i];
+ break;
+ default:
+ break;
+ }
+ }
+
+*/
+TEST_F(FissionClassTest, FissionSwitchStatement) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string source = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "B"
+ OpName %5 "A"
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 10
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Function %16
+ %18 = OpTypePointer Function %13
+ %19 = OpConstant %8 1
+ %2 = OpFunction %6 None %7
+ %20 = OpLabel
+ %3 = OpVariable %9 Function
+ %4 = OpVariable %17 Function
+ %5 = OpVariable %17 Function
+ OpStore %3 %10
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpPhi %8 %10 %20 %23 %24
+ OpLoopMerge %25 %24 None
+ OpBranch %26
+ %26 = OpLabel
+ %27 = OpSLessThan %12 %22 %11
+ OpBranchConditional %27 %28 %25
+ %28 = OpLabel
+ OpSelectionMerge %29 None
+ OpSwitch %22 %30 1 %31
+ %30 = OpLabel
+ %32 = OpAccessChain %18 %4 %22
+ %33 = OpLoad %13 %32
+ %34 = OpAccessChain %18 %5 %22
+ OpStore %34 %33
+ OpBranch %29
+ %31 = OpLabel
+ %35 = OpAccessChain %18 %5 %22
+ %36 = OpLoad %13 %35
+ %37 = OpAccessChain %18 %4 %22
+ OpStore %37 %36
+ OpBranch %29
+ %29 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ %23 = OpIAdd %8 %22 %19
+ OpStore %3 %23
+ OpBranch %21
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "B"
+OpName %5 "A"
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %8 10
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 1
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%3 = OpVariable %9 Function
+%4 = OpVariable %17 Function
+%5 = OpVariable %17 Function
+OpStore %3 %10
+OpBranch %38
+%38 = OpLabel
+%39 = OpPhi %8 %10 %20 %53 %52
+OpLoopMerge %54 %52 None
+OpBranch %40
+%40 = OpLabel
+%41 = OpSLessThan %12 %39 %11
+OpBranchConditional %41 %42 %54
+%42 = OpLabel
+OpSelectionMerge %51 None
+OpSwitch %39 %47 1 %43
+%43 = OpLabel
+OpBranch %51
+%47 = OpLabel
+%48 = OpAccessChain %18 %4 %39
+%49 = OpLoad %13 %48
+%50 = OpAccessChain %18 %5 %39
+OpStore %50 %49
+OpBranch %51
+%51 = OpLabel
+OpBranch %52
+%52 = OpLabel
+%53 = OpIAdd %8 %39 %19
+OpStore %3 %53
+OpBranch %38
+%54 = OpLabel
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %8 %10 %54 %23 %24
+OpLoopMerge %25 %24 None
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %22 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+OpSelectionMerge %29 None
+OpSwitch %22 %30 1 %31
+%30 = OpLabel
+OpBranch %29
+%31 = OpLabel
+%35 = OpAccessChain %18 %5 %22
+%36 = OpLoad %13 %35
+%37 = OpAccessChain %18 %4 %22
+OpStore %37 %36
+OpBranch %29
+%29 = OpLabel
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %8 %22 %19
+OpStore %3 %23
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopFissionPass>(source, expected, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/nested_loops.cpp b/test/opt/loop_optimizations/nested_loops.cpp
new file mode 100644
index 0000000..651cdef
--- /dev/null
+++ b/test/opt/loop_optimizations/nested_loops.cpp
@@ -0,0 +1,795 @@
+// 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 <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/iterator.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "source/opt/tree_iterator.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+bool Validate(const std::vector<uint32_t>& bin) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {bin.data(), bin.size()};
+ spv_result_t error = spvValidate(spvContext, &binary, &diagnostic);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ return error == 0;
+}
+
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ int i = 0;
+ for (; i < 10; ++i) {
+ int j = 0;
+ int k = 0;
+ for (; j < 11; ++j) {}
+ for (; k < 12; ++k) {}
+ }
+}
+*/
+TEST_F(PassClassTest, BasicVisitFromEntryPoint) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ OpName %5 "j"
+ OpName %6 "k"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %7 = OpTypeVoid
+ %8 = OpTypeFunction %7
+ %9 = OpTypeInt 32 1
+ %10 = OpTypePointer Function %9
+ %11 = OpConstant %9 0
+ %12 = OpConstant %9 10
+ %13 = OpTypeBool
+ %14 = OpConstant %9 11
+ %15 = OpConstant %9 1
+ %16 = OpConstant %9 12
+ %17 = OpTypeFloat 32
+ %18 = OpTypeVector %17 4
+ %19 = OpTypePointer Output %18
+ %3 = OpVariable %19 Output
+ %2 = OpFunction %7 None %8
+ %20 = OpLabel
+ %4 = OpVariable %10 Function
+ %5 = OpVariable %10 Function
+ %6 = OpVariable %10 Function
+ OpStore %4 %11
+ OpBranch %21
+ %21 = OpLabel
+ OpLoopMerge %22 %23 None
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpLoad %9 %4
+ %26 = OpSLessThan %13 %25 %12
+ OpBranchConditional %26 %27 %22
+ %27 = OpLabel
+ OpStore %5 %11
+ OpStore %6 %11
+ OpBranch %28
+ %28 = OpLabel
+ OpLoopMerge %29 %30 None
+ OpBranch %31
+ %31 = OpLabel
+ %32 = OpLoad %9 %5
+ %33 = OpSLessThan %13 %32 %14
+ OpBranchConditional %33 %34 %29
+ %34 = OpLabel
+ OpBranch %30
+ %30 = OpLabel
+ %35 = OpLoad %9 %5
+ %36 = OpIAdd %9 %35 %15
+ OpStore %5 %36
+ OpBranch %28
+ %29 = OpLabel
+ OpBranch %37
+ %37 = OpLabel
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %41 = OpLoad %9 %6
+ %42 = OpSLessThan %13 %41 %16
+ OpBranchConditional %42 %43 %38
+ %43 = OpLabel
+ OpBranch %39
+ %39 = OpLabel
+ %44 = OpLoad %9 %6
+ %45 = OpIAdd %9 %44 %15
+ OpStore %6 %45
+ OpBranch %37
+ %38 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %46 = OpLoad %9 %4
+ %47 = OpIAdd %9 %46 %15
+ OpStore %4 %47
+ OpBranch %21
+ %22 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ // Invalid basic block id.
+ EXPECT_EQ(ld[0u], nullptr);
+ // Not a loop header.
+ EXPECT_EQ(ld[20], nullptr);
+
+ Loop& parent_loop = *ld[21];
+ EXPECT_TRUE(parent_loop.HasNestedLoops());
+ EXPECT_FALSE(parent_loop.IsNested());
+ EXPECT_EQ(parent_loop.GetDepth(), 1u);
+ EXPECT_EQ(std::distance(parent_loop.begin(), parent_loop.end()), 2u);
+ EXPECT_EQ(parent_loop.GetHeaderBlock(), spvtest::GetBasicBlock(f, 21));
+ EXPECT_EQ(parent_loop.GetLatchBlock(), spvtest::GetBasicBlock(f, 23));
+ EXPECT_EQ(parent_loop.GetMergeBlock(), spvtest::GetBasicBlock(f, 22));
+
+ Loop& child_loop_1 = *ld[28];
+ EXPECT_FALSE(child_loop_1.HasNestedLoops());
+ EXPECT_TRUE(child_loop_1.IsNested());
+ EXPECT_EQ(child_loop_1.GetDepth(), 2u);
+ EXPECT_EQ(std::distance(child_loop_1.begin(), child_loop_1.end()), 0u);
+ EXPECT_EQ(child_loop_1.GetHeaderBlock(), spvtest::GetBasicBlock(f, 28));
+ EXPECT_EQ(child_loop_1.GetLatchBlock(), spvtest::GetBasicBlock(f, 30));
+ EXPECT_EQ(child_loop_1.GetMergeBlock(), spvtest::GetBasicBlock(f, 29));
+
+ Loop& child_loop_2 = *ld[37];
+ EXPECT_FALSE(child_loop_2.HasNestedLoops());
+ EXPECT_TRUE(child_loop_2.IsNested());
+ EXPECT_EQ(child_loop_2.GetDepth(), 2u);
+ EXPECT_EQ(std::distance(child_loop_2.begin(), child_loop_2.end()), 0u);
+ EXPECT_EQ(child_loop_2.GetHeaderBlock(), spvtest::GetBasicBlock(f, 37));
+ EXPECT_EQ(child_loop_2.GetLatchBlock(), spvtest::GetBasicBlock(f, 39));
+ EXPECT_EQ(child_loop_2.GetMergeBlock(), spvtest::GetBasicBlock(f, 38));
+}
+
+static void CheckLoopBlocks(Loop* loop,
+ std::unordered_set<uint32_t>* expected_ids) {
+ SCOPED_TRACE("Check loop " + std::to_string(loop->GetHeaderBlock()->id()));
+ for (uint32_t bb_id : loop->GetBlocks()) {
+ EXPECT_EQ(expected_ids->count(bb_id), 1u);
+ expected_ids->erase(bb_id);
+ }
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock()));
+ EXPECT_EQ(expected_ids->size(), 0u);
+}
+
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ int i = 0;
+ for (; i < 10; ++i) {
+ for (int j = 0; j < 11; ++j) {
+ if (j < 5) {
+ for (int k = 0; k < 12; ++k) {}
+ }
+ else {}
+ for (int k = 0; k < 12; ++k) {}
+ }
+ }
+}*/
+TEST_F(PassClassTest, TripleNestedLoop) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ OpName %5 "j"
+ OpName %6 "k"
+ OpName %7 "k"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %8 = OpTypeVoid
+ %9 = OpTypeFunction %8
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %12 = OpConstant %10 0
+ %13 = OpConstant %10 10
+ %14 = OpTypeBool
+ %15 = OpConstant %10 11
+ %16 = OpConstant %10 5
+ %17 = OpConstant %10 12
+ %18 = OpConstant %10 1
+ %19 = OpTypeFloat 32
+ %20 = OpTypeVector %19 4
+ %21 = OpTypePointer Output %20
+ %3 = OpVariable %21 Output
+ %2 = OpFunction %8 None %9
+ %22 = OpLabel
+ %4 = OpVariable %11 Function
+ %5 = OpVariable %11 Function
+ %6 = OpVariable %11 Function
+ %7 = OpVariable %11 Function
+ OpStore %4 %12
+ OpBranch %23
+ %23 = OpLabel
+ OpLoopMerge %24 %25 None
+ OpBranch %26
+ %26 = OpLabel
+ %27 = OpLoad %10 %4
+ %28 = OpSLessThan %14 %27 %13
+ OpBranchConditional %28 %29 %24
+ %29 = OpLabel
+ OpStore %5 %12
+ OpBranch %30
+ %30 = OpLabel
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+ %34 = OpLoad %10 %5
+ %35 = OpSLessThan %14 %34 %15
+ OpBranchConditional %35 %36 %31
+ %36 = OpLabel
+ %37 = OpLoad %10 %5
+ %38 = OpSLessThan %14 %37 %16
+ OpSelectionMerge %39 None
+ OpBranchConditional %38 %40 %39
+ %40 = OpLabel
+ OpStore %6 %12
+ OpBranch %41
+ %41 = OpLabel
+ OpLoopMerge %42 %43 None
+ OpBranch %44
+ %44 = OpLabel
+ %45 = OpLoad %10 %6
+ %46 = OpSLessThan %14 %45 %17
+ OpBranchConditional %46 %47 %42
+ %47 = OpLabel
+ OpBranch %43
+ %43 = OpLabel
+ %48 = OpLoad %10 %6
+ %49 = OpIAdd %10 %48 %18
+ OpStore %6 %49
+ OpBranch %41
+ %42 = OpLabel
+ OpBranch %39
+ %39 = OpLabel
+ OpStore %7 %12
+ OpBranch %50
+ %50 = OpLabel
+ OpLoopMerge %51 %52 None
+ OpBranch %53
+ %53 = OpLabel
+ %54 = OpLoad %10 %7
+ %55 = OpSLessThan %14 %54 %17
+ OpBranchConditional %55 %56 %51
+ %56 = OpLabel
+ OpBranch %52
+ %52 = OpLabel
+ %57 = OpLoad %10 %7
+ %58 = OpIAdd %10 %57 %18
+ OpStore %7 %58
+ OpBranch %50
+ %51 = OpLabel
+ OpBranch %32
+ %32 = OpLabel
+ %59 = OpLoad %10 %5
+ %60 = OpIAdd %10 %59 %18
+ OpStore %5 %60
+ OpBranch %30
+ %31 = OpLabel
+ OpBranch %25
+ %25 = OpLabel
+ %61 = OpLoad %10 %4
+ %62 = OpIAdd %10 %61 %18
+ OpStore %4 %62
+ OpBranch %23
+ %24 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ EXPECT_EQ(ld.NumLoops(), 4u);
+
+ // Invalid basic block id.
+ EXPECT_EQ(ld[0u], nullptr);
+ // Not in a loop.
+ EXPECT_EQ(ld[22], nullptr);
+
+ // Check that we can map basic block to the correct loop.
+ // The following block ids do not belong to a loop.
+ for (uint32_t bb_id : {22, 24}) EXPECT_EQ(ld[bb_id], nullptr);
+
+ {
+ std::unordered_set<uint32_t> basic_block_in_loop = {
+ {23, 26, 29, 30, 33, 36, 40, 41, 44, 47, 43,
+ 42, 39, 50, 53, 56, 52, 51, 32, 31, 25}};
+ Loop* loop = ld[23];
+ CheckLoopBlocks(loop, &basic_block_in_loop);
+
+ EXPECT_TRUE(loop->HasNestedLoops());
+ EXPECT_FALSE(loop->IsNested());
+ EXPECT_EQ(loop->GetDepth(), 1u);
+ EXPECT_EQ(std::distance(loop->begin(), loop->end()), 1u);
+ EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 22));
+ EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 23));
+ EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 25));
+ EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 24));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock()));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock()));
+ }
+
+ {
+ std::unordered_set<uint32_t> basic_block_in_loop = {
+ {30, 33, 36, 40, 41, 44, 47, 43, 42, 39, 50, 53, 56, 52, 51, 32}};
+ Loop* loop = ld[30];
+ CheckLoopBlocks(loop, &basic_block_in_loop);
+
+ EXPECT_TRUE(loop->HasNestedLoops());
+ EXPECT_TRUE(loop->IsNested());
+ EXPECT_EQ(loop->GetDepth(), 2u);
+ EXPECT_EQ(std::distance(loop->begin(), loop->end()), 2u);
+ EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 29));
+ EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 30));
+ EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 32));
+ EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 31));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock()));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock()));
+ }
+
+ {
+ std::unordered_set<uint32_t> basic_block_in_loop = {{41, 44, 47, 43}};
+ Loop* loop = ld[41];
+ CheckLoopBlocks(loop, &basic_block_in_loop);
+
+ EXPECT_FALSE(loop->HasNestedLoops());
+ EXPECT_TRUE(loop->IsNested());
+ EXPECT_EQ(loop->GetDepth(), 3u);
+ EXPECT_EQ(std::distance(loop->begin(), loop->end()), 0u);
+ EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 40));
+ EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 41));
+ EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 43));
+ EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 42));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock()));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock()));
+ }
+
+ {
+ std::unordered_set<uint32_t> basic_block_in_loop = {{50, 53, 56, 52}};
+ Loop* loop = ld[50];
+ CheckLoopBlocks(loop, &basic_block_in_loop);
+
+ EXPECT_FALSE(loop->HasNestedLoops());
+ EXPECT_TRUE(loop->IsNested());
+ EXPECT_EQ(loop->GetDepth(), 3u);
+ EXPECT_EQ(std::distance(loop->begin(), loop->end()), 0u);
+ EXPECT_EQ(loop->GetPreHeaderBlock(), spvtest::GetBasicBlock(f, 39));
+ EXPECT_EQ(loop->GetHeaderBlock(), spvtest::GetBasicBlock(f, 50));
+ EXPECT_EQ(loop->GetLatchBlock(), spvtest::GetBasicBlock(f, 52));
+ EXPECT_EQ(loop->GetMergeBlock(), spvtest::GetBasicBlock(f, 51));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetMergeBlock()));
+ EXPECT_FALSE(loop->IsInsideLoop(loop->GetPreHeaderBlock()));
+ }
+
+ // Make sure LoopDescriptor gives us the inner most loop when we query for
+ // loops.
+ for (const BasicBlock& bb : *f) {
+ if (Loop* loop = ld[&bb]) {
+ for (Loop& sub_loop :
+ make_range(++TreeDFIterator<Loop>(loop), TreeDFIterator<Loop>())) {
+ EXPECT_FALSE(sub_loop.IsInsideLoop(bb.id()));
+ }
+ }
+ }
+}
+
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ for (int i = 0; i < 10; ++i) {
+ for (int j = 0; j < 11; ++j) {
+ for (int k = 0; k < 11; ++k) {}
+ }
+ for (int k = 0; k < 12; ++k) {}
+ }
+}
+*/
+TEST_F(PassClassTest, LoopParentTest) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ OpName %5 "j"
+ OpName %6 "k"
+ OpName %7 "k"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %8 = OpTypeVoid
+ %9 = OpTypeFunction %8
+ %10 = OpTypeInt 32 1
+ %11 = OpTypePointer Function %10
+ %12 = OpConstant %10 0
+ %13 = OpConstant %10 10
+ %14 = OpTypeBool
+ %15 = OpConstant %10 11
+ %16 = OpConstant %10 1
+ %17 = OpConstant %10 12
+ %18 = OpTypeFloat 32
+ %19 = OpTypeVector %18 4
+ %20 = OpTypePointer Output %19
+ %3 = OpVariable %20 Output
+ %2 = OpFunction %8 None %9
+ %21 = OpLabel
+ %4 = OpVariable %11 Function
+ %5 = OpVariable %11 Function
+ %6 = OpVariable %11 Function
+ %7 = OpVariable %11 Function
+ OpStore %4 %12
+ OpBranch %22
+ %22 = OpLabel
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpLoad %10 %4
+ %27 = OpSLessThan %14 %26 %13
+ OpBranchConditional %27 %28 %23
+ %28 = OpLabel
+ OpStore %5 %12
+ OpBranch %29
+ %29 = OpLabel
+ OpLoopMerge %30 %31 None
+ OpBranch %32
+ %32 = OpLabel
+ %33 = OpLoad %10 %5
+ %34 = OpSLessThan %14 %33 %15
+ OpBranchConditional %34 %35 %30
+ %35 = OpLabel
+ OpStore %6 %12
+ OpBranch %36
+ %36 = OpLabel
+ OpLoopMerge %37 %38 None
+ OpBranch %39
+ %39 = OpLabel
+ %40 = OpLoad %10 %6
+ %41 = OpSLessThan %14 %40 %15
+ OpBranchConditional %41 %42 %37
+ %42 = OpLabel
+ OpBranch %38
+ %38 = OpLabel
+ %43 = OpLoad %10 %6
+ %44 = OpIAdd %10 %43 %16
+ OpStore %6 %44
+ OpBranch %36
+ %37 = OpLabel
+ OpBranch %31
+ %31 = OpLabel
+ %45 = OpLoad %10 %5
+ %46 = OpIAdd %10 %45 %16
+ OpStore %5 %46
+ OpBranch %29
+ %30 = OpLabel
+ OpStore %7 %12
+ OpBranch %47
+ %47 = OpLabel
+ OpLoopMerge %48 %49 None
+ OpBranch %50
+ %50 = OpLabel
+ %51 = OpLoad %10 %7
+ %52 = OpSLessThan %14 %51 %17
+ OpBranchConditional %52 %53 %48
+ %53 = OpLabel
+ OpBranch %49
+ %49 = OpLabel
+ %54 = OpLoad %10 %7
+ %55 = OpIAdd %10 %54 %16
+ OpStore %7 %55
+ OpBranch %47
+ %48 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ %56 = OpLoad %10 %4
+ %57 = OpIAdd %10 %56 %16
+ OpStore %4 %57
+ OpBranch %22
+ %23 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ EXPECT_EQ(ld.NumLoops(), 4u);
+
+ {
+ Loop& loop = *ld[22];
+ EXPECT_TRUE(loop.HasNestedLoops());
+ EXPECT_FALSE(loop.IsNested());
+ EXPECT_EQ(loop.GetDepth(), 1u);
+ EXPECT_EQ(loop.GetParent(), nullptr);
+ }
+
+ {
+ Loop& loop = *ld[29];
+ EXPECT_TRUE(loop.HasNestedLoops());
+ EXPECT_TRUE(loop.IsNested());
+ EXPECT_EQ(loop.GetDepth(), 2u);
+ EXPECT_EQ(loop.GetParent(), ld[22]);
+ }
+
+ {
+ Loop& loop = *ld[36];
+ EXPECT_FALSE(loop.HasNestedLoops());
+ EXPECT_TRUE(loop.IsNested());
+ EXPECT_EQ(loop.GetDepth(), 3u);
+ EXPECT_EQ(loop.GetParent(), ld[29]);
+ }
+
+ {
+ Loop& loop = *ld[47];
+ EXPECT_FALSE(loop.HasNestedLoops());
+ EXPECT_TRUE(loop.IsNested());
+ EXPECT_EQ(loop.GetDepth(), 2u);
+ EXPECT_EQ(loop.GetParent(), ld[22]);
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+The preheader of loop %33 and %41 were removed as well.
+
+#version 330 core
+void main() {
+ int a = 0;
+ for (int i = 0; i < 10; ++i) {
+ if (i == 0) {
+ a = 1;
+ } else {
+ a = 2;
+ }
+ for (int j = 0; j < 11; ++j) {
+ a++;
+ }
+ }
+ for (int k = 0; k < 12; ++k) {}
+}
+*/
+TEST_F(PassClassTest, CreatePreheaderTest) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %6 = OpTypePointer Function %5
+ %7 = OpConstant %5 0
+ %8 = OpConstant %5 10
+ %9 = OpTypeBool
+ %10 = OpConstant %5 1
+ %11 = OpConstant %5 2
+ %12 = OpConstant %5 11
+ %13 = OpConstant %5 12
+ %14 = OpUndef %5
+ %2 = OpFunction %3 None %4
+ %15 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %17 = OpPhi %5 %7 %15 %18 %19
+ %20 = OpPhi %5 %7 %15 %21 %19
+ %22 = OpPhi %5 %14 %15 %23 %19
+ OpLoopMerge %41 %19 None
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpSLessThan %9 %20 %8
+ OpBranchConditional %26 %27 %41
+ %27 = OpLabel
+ %28 = OpIEqual %9 %20 %7
+ OpSelectionMerge %33 None
+ OpBranchConditional %28 %30 %31
+ %30 = OpLabel
+ OpBranch %33
+ %31 = OpLabel
+ OpBranch %33
+ %33 = OpLabel
+ %18 = OpPhi %5 %10 %30 %11 %31 %34 %35
+ %23 = OpPhi %5 %7 %30 %7 %31 %36 %35
+ OpLoopMerge %37 %35 None
+ OpBranch %38
+ %38 = OpLabel
+ %39 = OpSLessThan %9 %23 %12
+ OpBranchConditional %39 %40 %37
+ %40 = OpLabel
+ %34 = OpIAdd %5 %18 %10
+ OpBranch %35
+ %35 = OpLabel
+ %36 = OpIAdd %5 %23 %10
+ OpBranch %33
+ %37 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %21 = OpIAdd %5 %20 %10
+ OpBranch %16
+ %41 = OpLabel
+ %42 = OpPhi %5 %7 %25 %43 %44
+ OpLoopMerge %45 %44 None
+ OpBranch %46
+ %46 = OpLabel
+ %47 = OpSLessThan %9 %42 %13
+ OpBranchConditional %47 %48 %45
+ %48 = OpLabel
+ OpBranch %44
+ %44 = OpLabel
+ %43 = OpIAdd %5 %42 %10
+ OpBranch %41
+ %45 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ // No invalidation of the cfg should occur during this test.
+ CFG* cfg = context->cfg();
+
+ EXPECT_EQ(ld.NumLoops(), 3u);
+
+ {
+ Loop& loop = *ld[16];
+ EXPECT_TRUE(loop.HasNestedLoops());
+ EXPECT_FALSE(loop.IsNested());
+ EXPECT_EQ(loop.GetDepth(), 1u);
+ EXPECT_EQ(loop.GetParent(), nullptr);
+ }
+
+ {
+ Loop& loop = *ld[33];
+ EXPECT_EQ(loop.GetPreHeaderBlock(), nullptr);
+ EXPECT_NE(loop.GetOrCreatePreHeaderBlock(), nullptr);
+ // Make sure the loop descriptor was properly updated.
+ EXPECT_EQ(ld[loop.GetPreHeaderBlock()], ld[16]);
+ {
+ const std::vector<uint32_t>& preds =
+ cfg->preds(loop.GetPreHeaderBlock()->id());
+ std::unordered_set<uint32_t> pred_set(preds.begin(), preds.end());
+ EXPECT_EQ(pred_set.size(), 2u);
+ EXPECT_TRUE(pred_set.count(30));
+ EXPECT_TRUE(pred_set.count(31));
+ // Check the phi instructions.
+ loop.GetPreHeaderBlock()->ForEachPhiInst([&pred_set](Instruction* phi) {
+ for (uint32_t i = 1; i < phi->NumInOperands(); i += 2) {
+ EXPECT_TRUE(pred_set.count(phi->GetSingleWordInOperand(i)));
+ }
+ });
+ }
+ {
+ const std::vector<uint32_t>& preds =
+ cfg->preds(loop.GetHeaderBlock()->id());
+ std::unordered_set<uint32_t> pred_set(preds.begin(), preds.end());
+ EXPECT_EQ(pred_set.size(), 2u);
+ EXPECT_TRUE(pred_set.count(loop.GetPreHeaderBlock()->id()));
+ EXPECT_TRUE(pred_set.count(35));
+ // Check the phi instructions.
+ loop.GetHeaderBlock()->ForEachPhiInst([&pred_set](Instruction* phi) {
+ for (uint32_t i = 1; i < phi->NumInOperands(); i += 2) {
+ EXPECT_TRUE(pred_set.count(phi->GetSingleWordInOperand(i)));
+ }
+ });
+ }
+ }
+
+ {
+ Loop& loop = *ld[41];
+ EXPECT_EQ(loop.GetPreHeaderBlock(), nullptr);
+ EXPECT_NE(loop.GetOrCreatePreHeaderBlock(), nullptr);
+ EXPECT_EQ(ld[loop.GetPreHeaderBlock()], nullptr);
+ EXPECT_EQ(cfg->preds(loop.GetPreHeaderBlock()->id()).size(), 1u);
+ EXPECT_EQ(cfg->preds(loop.GetPreHeaderBlock()->id())[0], 25u);
+ // Check the phi instructions.
+ loop.GetPreHeaderBlock()->ForEachPhiInst([](Instruction* phi) {
+ EXPECT_EQ(phi->NumInOperands(), 2u);
+ EXPECT_EQ(phi->GetSingleWordInOperand(1), 25u);
+ });
+ {
+ const std::vector<uint32_t>& preds =
+ cfg->preds(loop.GetHeaderBlock()->id());
+ std::unordered_set<uint32_t> pred_set(preds.begin(), preds.end());
+ EXPECT_EQ(pred_set.size(), 2u);
+ EXPECT_TRUE(pred_set.count(loop.GetPreHeaderBlock()->id()));
+ EXPECT_TRUE(pred_set.count(44));
+ // Check the phi instructions.
+ loop.GetHeaderBlock()->ForEachPhiInst([&pred_set](Instruction* phi) {
+ for (uint32_t i = 1; i < phi->NumInOperands(); i += 2) {
+ EXPECT_TRUE(pred_set.count(phi->GetSingleWordInOperand(i)));
+ }
+ });
+ }
+ }
+
+ // Make sure pre-header insertion leaves the module valid.
+ std::vector<uint32_t> bin;
+ context->module()->ToBinary(&bin, true);
+ EXPECT_TRUE(Validate(bin));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/pch_test_opt_loop.cpp b/test/opt/loop_optimizations/pch_test_opt_loop.cpp
new file mode 100644
index 0000000..f4ac7b2
--- /dev/null
+++ b/test/opt/loop_optimizations/pch_test_opt_loop.cpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2018 The Khronos Group 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 "pch_test_opt_loop.h"
diff --git a/test/opt/loop_optimizations/pch_test_opt_loop.h b/test/opt/loop_optimizations/pch_test_opt_loop.h
new file mode 100644
index 0000000..4e8106f
--- /dev/null
+++ b/test/opt/loop_optimizations/pch_test_opt_loop.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2018 The Khronos Group 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 "gmock/gmock.h"
+#include "source/opt/iterator.h"
+#include "source/opt/loop_dependence.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "source/opt/scalar_analysis.h"
+#include "source/opt/tree_iterator.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
diff --git a/test/opt/loop_optimizations/peeling.cpp b/test/opt/loop_optimizations/peeling.cpp
new file mode 100644
index 0000000..10d8add
--- /dev/null
+++ b/test/opt/loop_optimizations/peeling.cpp
@@ -0,0 +1,1186 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/loop_peeling.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using PeelingTest = PassTest<::testing::Test>;
+
+bool Validate(const std::vector<uint32_t>& bin) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {bin.data(), bin.size()};
+ spv_result_t error = spvValidate(spvContext, &binary, &diagnostic);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ return error == 0;
+}
+
+void Match(const std::string& checks, IRContext* context) {
+ // Silence unused warnings with !defined(SPIRV_EFFCE)
+ (void)checks;
+
+ std::vector<uint32_t> bin;
+ context->module()->ToBinary(&bin, true);
+ EXPECT_TRUE(Validate(bin));
+ std::string assembly;
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_2);
+ EXPECT_TRUE(
+ tools.Disassemble(bin, &assembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER))
+ << "Disassembling failed for shader:\n"
+ << assembly << std::endl;
+ auto match_result = effcee::Match(assembly, checks);
+ EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+ << match_result.message() << "\nChecking result:\n"
+ << assembly;
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+First test:
+#version 330 core
+void main() {
+ for(int i = 0; i < 10; ++i) {
+ if (i < 4)
+ break;
+ }
+}
+
+Second test (with a common sub-expression elimination):
+#version 330 core
+void main() {
+ for(int i = 0; i + 1 < 10; ++i) {
+ }
+}
+
+Third test:
+#version 330 core
+void main() {
+ int a[10];
+ for (int i = 0; a[i] != 0; i++) {}
+}
+
+Forth test:
+#version 330 core
+void main() {
+ for (long i = 0; i < 10; i++) {}
+}
+
+Fifth test:
+#version 330 core
+void main() {
+ for (float i = 0; i < 10; i++) {}
+}
+
+Sixth test:
+#version 450
+layout(location = 0)out float o;
+void main() {
+ o = 0.0;
+ for( int i = 0; true; i++ ) {
+ o += 1.0;
+ if (i > 10) break;
+ }
+}
+*/
+TEST_F(PeelingTest, CannotPeel) {
+ // Build the given SPIR-V program in |text|, take the first loop in the first
+ // function and test that it is not peelable. |loop_count_id| is the id
+ // representing the loop count, if equals to 0, then the function build a 10
+ // constant as loop count.
+ auto test_cannot_peel = [](const std::string& text, uint32_t loop_count_id) {
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ Instruction* loop_count = nullptr;
+ if (loop_count_id) {
+ loop_count = context->get_def_use_mgr()->GetDef(loop_count_id);
+ } else {
+ InstructionBuilder builder(context.get(), &*f.begin());
+ // Exit condition.
+ loop_count = builder.GetSintConstant(10);
+ }
+
+ LoopPeeling peel(&*ld.begin(), loop_count);
+ EXPECT_FALSE(peel.CanPeelLoop());
+ };
+ {
+ SCOPED_TRACE("loop with break");
+
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_4 = OpConstant %int 4
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %28 = OpPhi %int %int_0 %5 %27 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %bool %28 %int_10
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %21 = OpSLessThan %bool %28 %int_4
+ OpSelectionMerge %23 None
+ OpBranchConditional %21 %22 %23
+ %22 = OpLabel
+ OpBranch %12
+ %23 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %27 = OpIAdd %int %28 %int_1
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ test_cannot_peel(text, 0);
+ }
+
+ {
+ SCOPED_TRACE("Ambiguous iterator update");
+
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %23 = OpPhi %int %int_0 %5 %17 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %17 = OpIAdd %int %23 %int_1
+ %20 = OpSLessThan %bool %17 %int_10
+ OpBranchConditional %20 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ test_cannot_peel(text, 0);
+ }
+
+ {
+ SCOPED_TRACE("No loop static bounds");
+
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %i "i"
+ OpName %a "a"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %uint = OpTypeInt 32 0
+ %uint_10 = OpConstant %uint 10
+%_arr_int_uint_10 = OpTypeArray %int %uint_10
+%_ptr_Function__arr_int_uint_10 = OpTypePointer Function %_arr_int_uint_10
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %i = OpVariable %_ptr_Function_int Function
+ %a = OpVariable %_ptr_Function__arr_int_uint_10 Function
+ OpStore %i %int_0
+ OpBranch %10
+ %10 = OpLabel
+ %28 = OpPhi %int %int_0 %5 %27 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %21 = OpAccessChain %_ptr_Function_int %a %28
+ %22 = OpLoad %int %21
+ %24 = OpINotEqual %bool %22 %int_0
+ OpBranchConditional %24 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %27 = OpIAdd %int %28 %int_1
+ OpStore %i %27
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ test_cannot_peel(text, 22);
+ }
+ {
+ SCOPED_TRACE("Int 64 type for conditions");
+
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginLowerLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ %6 = OpTypeVoid
+ %3 = OpTypeFunction %6
+ %7 = OpTypeInt 64 1
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %15 = OpConstant %7 10
+ %16 = OpTypeBool
+ %17 = OpConstant %7 1
+ %2 = OpFunction %6 None %3
+ %5 = OpLabel
+ %4 = OpVariable %8 Function
+ OpStore %4 %9
+ OpBranch %10
+ %10 = OpLabel
+ %22 = OpPhi %7 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %16 %22 %15
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %7 %22 %17
+ OpStore %4 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // %15 is a constant for a 64 int. Currently rejected.
+ test_cannot_peel(text, 15);
+ }
+ {
+ SCOPED_TRACE("Float type for conditions");
+
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginLowerLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %4 "i"
+ %6 = OpTypeVoid
+ %3 = OpTypeFunction %6
+ %7 = OpTypeFloat 32
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %15 = OpConstant %7 10
+ %16 = OpTypeBool
+ %17 = OpConstant %7 1
+ %2 = OpFunction %6 None %3
+ %5 = OpLabel
+ %4 = OpVariable %8 Function
+ OpStore %4 %9
+ OpBranch %10
+ %10 = OpLabel
+ %22 = OpPhi %7 %9 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpFOrdLessThan %16 %22 %15
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpFAdd %7 %22 %17
+ OpStore %4 %21
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // %15 is a constant for a float. Currently rejected.
+ test_cannot_peel(text, 15);
+ }
+ {
+ SCOPED_TRACE("Side effect before exit");
+
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %o
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %o "o"
+ OpName %i "i"
+ OpDecorate %o Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+ %o = OpVariable %_ptr_Output_float Output
+ %float_0 = OpConstant %float 0
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %float_1 = OpConstant %float 1
+ %int_10 = OpConstant %int 10
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %i = OpVariable %_ptr_Function_int Function
+ OpStore %o %float_0
+ OpStore %i %int_0
+ OpBranch %14
+ %14 = OpLabel
+ %33 = OpPhi %int %int_0 %5 %32 %17
+ OpLoopMerge %16 %17 None
+ OpBranch %15
+ %15 = OpLabel
+ %22 = OpLoad %float %o
+ %23 = OpFAdd %float %22 %float_1
+ OpStore %o %23
+ %26 = OpSGreaterThan %bool %33 %int_10
+ OpSelectionMerge %28 None
+ OpBranchConditional %26 %27 %28
+ %27 = OpLabel
+ OpBranch %16
+ %28 = OpLabel
+ OpBranch %17
+ %17 = OpLabel
+ %32 = OpIAdd %int %33 %int_1
+ OpStore %i %32
+ OpBranch %14
+ %16 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ test_cannot_peel(text, 0);
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int i = 0;
+ for (; i < 10; i++) {}
+}
+*/
+TEST_F(PeelingTest, SimplePeeling) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %22 = OpPhi %int %int_0 %5 %21 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %bool %22 %int_10
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %21 = OpIAdd %int %22 %int_1
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ // Peel before.
+ {
+ SCOPED_TRACE("Peel before");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ InstructionBuilder builder(context.get(), &*f.begin());
+ // Exit condition.
+ Instruction* ten_cst = builder.GetSintConstant(10);
+
+ LoopPeeling peel(&*ld.begin(), ten_cst);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelBefore(2);
+
+ const std::string check = R"(
+CHECK: [[CST_TEN:%\w+]] = OpConstant {{%\w+}} 10
+CHECK: [[CST_TWO:%\w+]] = OpConstant {{%\w+}} 2
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} [[CST_TWO]] [[CST_TEN]]
+CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] [[CST_TWO]] [[CST_TEN]]
+CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[AFTER_LOOP_PREHEADER]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]]
+CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[i]] [[AFTER_LOOP_PREHEADER]]
+CHECK-NEXT: OpLoopMerge
+)";
+
+ Match(check, context.get());
+ }
+
+ // Peel after.
+ {
+ SCOPED_TRACE("Peel after");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ InstructionBuilder builder(context.get(), &*f.begin());
+ // Exit condition.
+ Instruction* ten_cst = builder.GetSintConstant(10);
+
+ LoopPeeling peel(&*ld.begin(), ten_cst);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelAfter(2);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}}
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]]
+CHECK: [[BEFORE_LOOP]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[TMP:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT]] {{%\w+}}
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[TMP]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[BEFORE_LOOP_MERGE]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]]
+CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[IF_MERGE]] = OpLabel
+CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I]] [[BEFORE_LOOP_MERGE]]
+CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]]
+CHECK-NEXT: OpLoopMerge
+
+)";
+
+ Match(check, context.get());
+ }
+
+ // Same as above, but reuse the induction variable.
+ // Peel before.
+ {
+ SCOPED_TRACE("Peel before with IV reuse");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ InstructionBuilder builder(context.get(), &*f.begin());
+ // Exit condition.
+ Instruction* ten_cst = builder.GetSintConstant(10);
+
+ LoopPeeling peel(&*ld.begin(), ten_cst,
+ context->get_def_use_mgr()->GetDef(22));
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelBefore(2);
+
+ const std::string check = R"(
+CHECK: [[CST_TEN:%\w+]] = OpConstant {{%\w+}} 10
+CHECK: [[CST_TWO:%\w+]] = OpConstant {{%\w+}} 2
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} [[CST_TWO]] [[CST_TEN]]
+CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] [[CST_TWO]] [[CST_TEN]]
+CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel
+CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[i]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[AFTER_LOOP_PREHEADER]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[i]] [[AFTER_LOOP_PREHEADER]]
+CHECK-NEXT: OpLoopMerge
+)";
+
+ Match(check, context.get());
+ }
+
+ // Peel after.
+ {
+ SCOPED_TRACE("Peel after IV reuse");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ InstructionBuilder builder(context.get(), &*f.begin());
+ // Exit condition.
+ Instruction* ten_cst = builder.GetSintConstant(10);
+
+ LoopPeeling peel(&*ld.begin(), ten_cst,
+ context->get_def_use_mgr()->GetDef(22));
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelAfter(2);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}}
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]]
+CHECK: [[BEFORE_LOOP]] = OpLabel
+CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[TMP:%\w+]] = OpIAdd {{%\w+}} [[I]] {{%\w+}}
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[TMP]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[BEFORE_LOOP_MERGE]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[IF_MERGE]] = OpLabel
+CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I]] [[BEFORE_LOOP_MERGE]]
+CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]]
+CHECK-NEXT: OpLoopMerge
+
+)";
+
+ Match(check, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int a[10];
+ int n = a[0];
+ for(int i = 0; i < n; ++i) {}
+}
+*/
+TEST_F(PeelingTest, PeelingUncountable) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %a "a"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %uint = OpTypeInt 32 0
+ %uint_10 = OpConstant %uint 10
+%_arr_int_uint_10 = OpTypeArray %int %uint_10
+%_ptr_Function__arr_int_uint_10 = OpTypePointer Function %_arr_int_uint_10
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %a = OpVariable %_ptr_Function__arr_int_uint_10 Function
+ %15 = OpAccessChain %_ptr_Function_int %a %int_0
+ %16 = OpLoad %int %15
+ OpBranch %18
+ %18 = OpLabel
+ %30 = OpPhi %int %int_0 %5 %29 %21
+ OpLoopMerge %20 %21 None
+ OpBranch %22
+ %22 = OpLabel
+ %26 = OpSLessThan %bool %30 %16
+ OpBranchConditional %26 %19 %20
+ %19 = OpLabel
+ OpBranch %21
+ %21 = OpLabel
+ %29 = OpIAdd %int %30 %int_1
+ OpBranch %18
+ %20 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ // Peel before.
+ {
+ SCOPED_TRACE("Peel before");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ Instruction* loop_count = context->get_def_use_mgr()->GetDef(16);
+ EXPECT_EQ(loop_count->opcode(), SpvOpLoad);
+
+ LoopPeeling peel(&*ld.begin(), loop_count);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelBefore(1);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[LOOP_COUNT:%\w+]] = OpLoad
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} {{%\w+}} [[LOOP_COUNT]]
+CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] {{%\w+}} [[LOOP_COUNT]]
+CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[AFTER_LOOP_PREHEADER]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]]
+CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[i]] [[AFTER_LOOP_PREHEADER]]
+CHECK-NEXT: OpLoopMerge
+)";
+
+ Match(check, context.get());
+ }
+
+ // Peel after.
+ {
+ SCOPED_TRACE("Peel after");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ Instruction* loop_count = context->get_def_use_mgr()->GetDef(16);
+ EXPECT_EQ(loop_count->opcode(), SpvOpLoad);
+
+ LoopPeeling peel(&*ld.begin(), loop_count);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelAfter(1);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}}
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]]
+CHECK: [[BEFORE_LOOP]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[TMP:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT]] {{%\w+}}
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[TMP]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[BEFORE_LOOP_MERGE]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]]
+CHECK-NEXT: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[IF_MERGE]] = OpLabel
+CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I]] [[BEFORE_LOOP_MERGE]]
+CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]]
+CHECK-NEXT: OpLoopMerge
+
+)";
+
+ Match(check, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int i = 0;
+ do {
+ i++;
+ } while (i < 10);
+}
+*/
+TEST_F(PeelingTest, DoWhilePeeling) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %21 = OpPhi %int %int_0 %5 %16 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %11
+ %11 = OpLabel
+ %16 = OpIAdd %int %21 %int_1
+ OpBranch %13
+ %13 = OpLabel
+ %20 = OpSLessThan %bool %16 %int_10
+ OpBranchConditional %20 %10 %12
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ // Peel before.
+ {
+ SCOPED_TRACE("Peel before");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+ InstructionBuilder builder(context.get(), &*f.begin());
+ // Exit condition.
+ Instruction* ten_cst = builder.GetUintConstant(10);
+
+ LoopPeeling peel(&*ld.begin(), ten_cst);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelBefore(2);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpULessThan {{%\w+}}
+CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]]
+CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]]
+CHECK: [[BE]] = OpLabel
+CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpULessThan {{%\w+}} [[DUMMY_IT_1]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] [[BEFORE_LOOP]] [[AFTER_LOOP_PREHEADER]]
+
+CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[I_1]] [[AFTER_LOOP_PREHEADER]]
+CHECK-NEXT: OpLoopMerge
+)";
+
+ Match(check, context.get());
+ }
+
+ // Peel after.
+ {
+ SCOPED_TRACE("Peel after");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ InstructionBuilder builder(context.get(), &*f.begin());
+ // Exit condition.
+ Instruction* ten_cst = builder.GetUintConstant(10);
+
+ LoopPeeling peel(&*ld.begin(), ten_cst);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelAfter(2);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpULessThan {{%\w+}}
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]]
+CHECK: [[BEFORE_LOOP]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]]
+CHECK: [[BE]] = OpLabel
+CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: [[EXIT_VAL:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT_1]]
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpULessThan {{%\w+}} [[EXIT_VAL]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] [[BEFORE_LOOP]] [[BEFORE_LOOP_MERGE]]
+
+CHECK: [[IF_MERGE]] = OpLabel
+CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I_1]] [[BEFORE_LOOP_MERGE]]
+CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]]
+CHECK-NEXT: OpLoopMerge
+)";
+
+ Match(check, context.get());
+ }
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int a[10];
+ int n = a[0];
+ for(int i = 0; i < n; ++i) {}
+}
+*/
+TEST_F(PeelingTest, PeelingLoopWithStore) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %o %n
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %o "o"
+ OpName %end "end"
+ OpName %n "n"
+ OpName %i "i"
+ OpDecorate %o Location 0
+ OpDecorate %n Flat
+ OpDecorate %n Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+ %o = OpVariable %_ptr_Output_float Output
+ %float_0 = OpConstant %float 0
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%_ptr_Input_int = OpTypePointer Input %int
+ %n = OpVariable %_ptr_Input_int Input
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+ %float_1 = OpConstant %float 1
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %end = OpVariable %_ptr_Function_int Function
+ %i = OpVariable %_ptr_Function_int Function
+ OpStore %o %float_0
+ %15 = OpLoad %int %n
+ OpStore %end %15
+ OpStore %i %int_0
+ OpBranch %18
+ %18 = OpLabel
+ %33 = OpPhi %int %int_0 %5 %32 %21
+ OpLoopMerge %20 %21 None
+ OpBranch %22
+ %22 = OpLabel
+ %26 = OpSLessThan %bool %33 %15
+ OpBranchConditional %26 %19 %20
+ %19 = OpLabel
+ %28 = OpLoad %float %o
+ %29 = OpFAdd %float %28 %float_1
+ OpStore %o %29
+ OpBranch %21
+ %21 = OpLabel
+ %32 = OpIAdd %int %33 %int_1
+ OpStore %i %32
+ OpBranch %18
+ %20 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ // Peel before.
+ {
+ SCOPED_TRACE("Peel before");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ Instruction* loop_count = context->get_def_use_mgr()->GetDef(15);
+ EXPECT_EQ(loop_count->opcode(), SpvOpLoad);
+
+ LoopPeeling peel(&*ld.begin(), loop_count);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelBefore(1);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[LOOP_COUNT:%\w+]] = OpLoad
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}} {{%\w+}} [[LOOP_COUNT]]
+CHECK-NEXT: [[LOOP_COUNT:%\w+]] = OpSelect {{%\w+}} [[MIN_LOOP_COUNT]] {{%\w+}} [[LOOP_COUNT]]
+CHECK: [[BEFORE_LOOP:%\w+]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[i:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[AFTER_LOOP_PREHEADER:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[AFTER_LOOP_PREHEADER]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[i]]
+CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[AFTER_LOOP_PREHEADER]] = OpLabel
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[AFTER_LOOP:%\w+]] [[IF_MERGE]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[i]] [[AFTER_LOOP_PREHEADER]]
+CHECK-NEXT: OpLoopMerge
+)";
+
+ Match(check, context.get());
+ }
+
+ // Peel after.
+ {
+ SCOPED_TRACE("Peel after");
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function& f = *module->begin();
+ LoopDescriptor& ld = *context->GetLoopDescriptor(&f);
+
+ EXPECT_EQ(ld.NumLoops(), 1u);
+
+ Instruction* loop_count = context->get_def_use_mgr()->GetDef(15);
+ EXPECT_EQ(loop_count->opcode(), SpvOpLoad);
+
+ LoopPeeling peel(&*ld.begin(), loop_count);
+ EXPECT_TRUE(peel.CanPeelLoop());
+ peel.PeelAfter(1);
+
+ const std::string check = R"(
+CHECK: OpFunction
+CHECK-NEXT: [[ENTRY:%\w+]] = OpLabel
+CHECK: [[MIN_LOOP_COUNT:%\w+]] = OpSLessThan {{%\w+}}
+CHECK-NEXT: OpSelectionMerge [[IF_MERGE:%\w+]]
+CHECK-NEXT: OpBranchConditional [[MIN_LOOP_COUNT]] [[BEFORE_LOOP:%\w+]] [[IF_MERGE]]
+CHECK: [[BEFORE_LOOP]] = OpLabel
+CHECK-NEXT: [[DUMMY_IT:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[DUMMY_IT_1:%\w+]] [[BE:%\w+]]
+CHECK-NEXT: [[I:%\w+]] = OpPhi {{%\w+}} {{%\w+}} [[ENTRY]] [[I_1:%\w+]] [[BE]]
+CHECK-NEXT: OpLoopMerge [[BEFORE_LOOP_MERGE:%\w+]] [[BE]] None
+CHECK: [[COND_BLOCK:%\w+]] = OpLabel
+CHECK-NEXT: OpSLessThan
+CHECK-NEXT: [[TMP:%\w+]] = OpIAdd {{%\w+}} [[DUMMY_IT]] {{%\w+}}
+CHECK-NEXT: [[EXIT_COND:%\w+]] = OpSLessThan {{%\w+}} [[TMP]]
+CHECK-NEXT: OpBranchConditional [[EXIT_COND]] {{%\w+}} [[BEFORE_LOOP_MERGE]]
+CHECK: [[I_1]] = OpIAdd {{%\w+}} [[I]]
+CHECK: [[DUMMY_IT_1]] = OpIAdd {{%\w+}} [[DUMMY_IT]]
+CHECK-NEXT: OpBranch [[BEFORE_LOOP]]
+
+CHECK: [[IF_MERGE]] = OpLabel
+CHECK-NEXT: [[TMP:%\w+]] = OpPhi {{%\w+}} [[I]] [[BEFORE_LOOP_MERGE]]
+CHECK-NEXT: OpBranch [[AFTER_LOOP:%\w+]]
+
+CHECK: [[AFTER_LOOP]] = OpLabel
+CHECK-NEXT: OpPhi {{%\w+}} {{%\w+}} {{%\w+}} [[TMP]] [[IF_MERGE]]
+CHECK-NEXT: OpLoopMerge
+
+)";
+
+ Match(check, context.get());
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/peeling_pass.cpp b/test/opt/loop_optimizations/peeling_pass.cpp
new file mode 100644
index 0000000..284ad83
--- /dev/null
+++ b/test/opt/loop_optimizations/peeling_pass.cpp
@@ -0,0 +1,1099 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/ir_builder.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/loop_peeling.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+class PeelingPassTest : public PassTest<::testing::Test> {
+ public:
+ // Generic routine to run the loop peeling pass and check
+ LoopPeelingPass::LoopPeelingStats AssembleAndRunPeelingTest(
+ const std::string& text_head, const std::string& text_tail, SpvOp opcode,
+ const std::string& res_id, const std::string& op1,
+ const std::string& op2) {
+ std::string opcode_str;
+ switch (opcode) {
+ case SpvOpSLessThan:
+ opcode_str = "OpSLessThan";
+ break;
+ case SpvOpSGreaterThan:
+ opcode_str = "OpSGreaterThan";
+ break;
+ case SpvOpSLessThanEqual:
+ opcode_str = "OpSLessThanEqual";
+ break;
+ case SpvOpSGreaterThanEqual:
+ opcode_str = "OpSGreaterThanEqual";
+ break;
+ case SpvOpIEqual:
+ opcode_str = "OpIEqual";
+ break;
+ case SpvOpINotEqual:
+ opcode_str = "OpINotEqual";
+ break;
+ default:
+ assert(false && "Unhandled");
+ break;
+ }
+ std::string test_cond =
+ res_id + " = " + opcode_str + " %bool " + op1 + " " + op2 + "\n";
+
+ LoopPeelingPass::LoopPeelingStats stats;
+ SinglePassRunAndDisassemble<LoopPeelingPass>(
+ text_head + test_cond + text_tail, true, true, &stats);
+
+ return stats;
+ }
+
+ // Generic routine to run the loop peeling pass and check
+ LoopPeelingPass::LoopPeelingStats RunPeelingTest(
+ const std::string& text_head, const std::string& text_tail, SpvOp opcode,
+ const std::string& res_id, const std::string& op1, const std::string& op2,
+ size_t nb_of_loops) {
+ LoopPeelingPass::LoopPeelingStats stats = AssembleAndRunPeelingTest(
+ text_head, text_tail, opcode, res_id, op1, op2);
+
+ Function& f = *context()->module()->begin();
+ LoopDescriptor& ld = *context()->GetLoopDescriptor(&f);
+ EXPECT_EQ(ld.NumLoops(), nb_of_loops);
+
+ return stats;
+ }
+
+ using PeelTraceType =
+ std::vector<std::pair<LoopPeelingPass::PeelDirection, uint32_t>>;
+
+ void BuildAndCheckTrace(const std::string& text_head,
+ const std::string& text_tail, SpvOp opcode,
+ const std::string& res_id, const std::string& op1,
+ const std::string& op2,
+ const PeelTraceType& expected_peel_trace,
+ size_t expected_nb_of_loops) {
+ auto stats = RunPeelingTest(text_head, text_tail, opcode, res_id, op1, op2,
+ expected_nb_of_loops);
+
+ EXPECT_EQ(stats.peeled_loops_.size(), expected_peel_trace.size());
+ if (stats.peeled_loops_.size() != expected_peel_trace.size()) {
+ return;
+ }
+
+ PeelTraceType::const_iterator expected_trace_it =
+ expected_peel_trace.begin();
+ decltype(stats.peeled_loops_)::const_iterator stats_it =
+ stats.peeled_loops_.begin();
+
+ while (expected_trace_it != expected_peel_trace.end()) {
+ EXPECT_EQ(expected_trace_it->first, std::get<1>(*stats_it));
+ EXPECT_EQ(expected_trace_it->second, std::get<2>(*stats_it));
+ ++expected_trace_it;
+ ++stats_it;
+ }
+ }
+};
+
+/*
+Test are derivation of the following generated test from the following GLSL +
+--eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int a = 0;
+ for(int i = 1; i < 10; i += 2) {
+ if (i < 3) {
+ a += 2;
+ }
+ }
+}
+
+The condition is interchanged to test < > <= >= == and peel before/after
+opportunities.
+*/
+TEST_F(PeelingPassTest, PeelingPassBasic) {
+ const std::string text_head = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %a "a"
+ OpName %i "i"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %bool = OpTypeBool
+ %int_20 = OpConstant %int 20
+ %int_19 = OpConstant %int 19
+ %int_18 = OpConstant %int 18
+ %int_17 = OpConstant %int 17
+ %int_16 = OpConstant %int 16
+ %int_15 = OpConstant %int 15
+ %int_14 = OpConstant %int 14
+ %int_13 = OpConstant %int 13
+ %int_12 = OpConstant %int 12
+ %int_11 = OpConstant %int 11
+ %int_10 = OpConstant %int 10
+ %int_9 = OpConstant %int 9
+ %int_8 = OpConstant %int 8
+ %int_7 = OpConstant %int 7
+ %int_6 = OpConstant %int 6
+ %int_5 = OpConstant %int 5
+ %int_4 = OpConstant %int 4
+ %int_3 = OpConstant %int 3
+ %int_2 = OpConstant %int 2
+ %int_1 = OpConstant %int 1
+ %int_0 = OpConstant %int 0
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %a = OpVariable %_ptr_Function_int Function
+ %i = OpVariable %_ptr_Function_int Function
+ OpStore %a %int_0
+ OpStore %i %int_0
+ OpBranch %11
+ %11 = OpLabel
+ %31 = OpPhi %int %int_0 %5 %33 %14
+ %32 = OpPhi %int %int_1 %5 %30 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %bool %32 %int_20
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ )";
+ const std::string text_tail = R"(
+ OpSelectionMerge %24 None
+ OpBranchConditional %22 %23 %24
+ %23 = OpLabel
+ %27 = OpIAdd %int %31 %int_2
+ OpStore %a %27
+ OpBranch %24
+ %24 = OpLabel
+ %33 = OpPhi %int %31 %12 %27 %23
+ OpBranch %14
+ %14 = OpLabel
+ %30 = OpIAdd %int %32 %int_2
+ OpStore %i %30
+ OpBranch %11
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto run_test = [&text_head, &text_tail, this](SpvOp opcode,
+ const std::string& op1,
+ const std::string& op2) {
+ auto stats =
+ RunPeelingTest(text_head, text_tail, opcode, "%22", op1, op2, 2);
+
+ EXPECT_EQ(stats.peeled_loops_.size(), 1u);
+ if (stats.peeled_loops_.size() != 1u)
+ return std::pair<LoopPeelingPass::PeelDirection, uint32_t>{
+ LoopPeelingPass::PeelDirection::kNone, 0};
+
+ return std::pair<LoopPeelingPass::PeelDirection, uint32_t>{
+ std::get<1>(*stats.peeled_loops_.begin()),
+ std::get<2>(*stats.peeled_loops_.begin())};
+ };
+
+ // Test LT
+ // Peel before by a factor of 2.
+ {
+ SCOPED_TRACE("Peel before iv < 4");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%32", "%int_4");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 4 > iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%int_4", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before iv < 5");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%32", "%int_5");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 5 > iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%int_5", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Peel after by a factor of 2.
+ {
+ SCOPED_TRACE("Peel after iv < 16");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%32", "%int_16");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 16 > iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%int_16", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after iv < 17");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%32", "%int_17");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 17 > iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%int_17", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Test GT
+ // Peel before by a factor of 2.
+ {
+ SCOPED_TRACE("Peel before iv > 5");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%32", "%int_5");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 5 < iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%int_5", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before iv > 4");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%32", "%int_4");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 4 < iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%int_4", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Peel after by a factor of 2.
+ {
+ SCOPED_TRACE("Peel after iv > 16");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%32", "%int_16");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 16 < iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%int_16", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after iv > 17");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThan, "%32", "%int_17");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 17 < iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThan, "%int_17", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Test LE
+ // Peel before by a factor of 2.
+ {
+ SCOPED_TRACE("Peel before iv <= 4");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%32", "%int_4");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 4 => iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%int_4", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before iv <= 3");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%32", "%int_3");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 3 => iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%int_3", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Peel after by a factor of 2.
+ {
+ SCOPED_TRACE("Peel after iv <= 16");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%32", "%int_16");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 16 => iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%int_16", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after iv <= 15");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%32", "%int_15");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 15 => iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%int_15", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Test GE
+ // Peel before by a factor of 2.
+ {
+ SCOPED_TRACE("Peel before iv >= 5");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%32", "%int_5");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 35 >= iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%int_5", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before iv >= 4");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%32", "%int_4");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel before 4 <= iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%int_4", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Peel after by a factor of 2.
+ {
+ SCOPED_TRACE("Peel after iv >= 17");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%32", "%int_17");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 17 <= iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%int_17", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after iv >= 16");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSGreaterThanEqual, "%32", "%int_16");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+ {
+ SCOPED_TRACE("Peel after 16 <= iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpSLessThanEqual, "%int_16", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 2u);
+ }
+
+ // Test EQ
+ // Peel before by a factor of 1.
+ {
+ SCOPED_TRACE("Peel before iv == 1");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpIEqual, "%32", "%int_1");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+ {
+ SCOPED_TRACE("Peel before 1 == iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpIEqual, "%int_1", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+
+ // Peel after by a factor of 1.
+ {
+ SCOPED_TRACE("Peel after iv == 19");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpIEqual, "%32", "%int_19");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+ {
+ SCOPED_TRACE("Peel after 19 == iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpIEqual, "%int_19", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+
+ // Test NE
+ // Peel before by a factor of 1.
+ {
+ SCOPED_TRACE("Peel before iv != 1");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpINotEqual, "%32", "%int_1");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+ {
+ SCOPED_TRACE("Peel before 1 != iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpINotEqual, "%int_1", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kBefore);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+
+ // Peel after by a factor of 1.
+ {
+ SCOPED_TRACE("Peel after iv != 19");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpINotEqual, "%32", "%int_19");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+ {
+ SCOPED_TRACE("Peel after 19 != iv");
+
+ std::pair<LoopPeelingPass::PeelDirection, uint32_t> peel_info =
+ run_test(SpvOpINotEqual, "%int_19", "%32");
+ EXPECT_EQ(peel_info.first, LoopPeelingPass::PeelDirection::kAfter);
+ EXPECT_EQ(peel_info.second, 1u);
+ }
+
+ // No peel.
+ {
+ SCOPED_TRACE("No Peel: 20 => iv");
+
+ auto stats = RunPeelingTest(text_head, text_tail, SpvOpSLessThanEqual,
+ "%22", "%int_20", "%32", 1);
+
+ EXPECT_EQ(stats.peeled_loops_.size(), 0u);
+ }
+}
+
+/*
+Test are derivation of the following generated test from the following GLSL +
+--eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int a = 0;
+ for(int i = 0; i < 10; ++i) {
+ if (i < 3) {
+ a += 2;
+ }
+ if (i < 1) {
+ a += 2;
+ }
+ }
+}
+
+The condition is interchanged to test < > <= >= == and peel before/after
+opportunities.
+*/
+TEST_F(PeelingPassTest, MultiplePeelingPass) {
+ const std::string text_head = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %a "a"
+ OpName %i "i"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %bool = OpTypeBool
+ %int_10 = OpConstant %int 10
+ %int_9 = OpConstant %int 9
+ %int_8 = OpConstant %int 8
+ %int_7 = OpConstant %int 7
+ %int_6 = OpConstant %int 6
+ %int_5 = OpConstant %int 5
+ %int_4 = OpConstant %int 4
+ %int_3 = OpConstant %int 3
+ %int_2 = OpConstant %int 2
+ %int_1 = OpConstant %int 1
+ %int_0 = OpConstant %int 0
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %a = OpVariable %_ptr_Function_int Function
+ %i = OpVariable %_ptr_Function_int Function
+ OpStore %a %int_0
+ OpStore %i %int_0
+ OpBranch %11
+ %11 = OpLabel
+ %37 = OpPhi %int %int_0 %5 %40 %14
+ %38 = OpPhi %int %int_0 %5 %36 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %bool %38 %int_10
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ )";
+ const std::string text_tail = R"(
+ OpSelectionMerge %24 None
+ OpBranchConditional %22 %23 %24
+ %23 = OpLabel
+ %27 = OpIAdd %int %37 %int_2
+ OpStore %a %27
+ OpBranch %24
+ %24 = OpLabel
+ %39 = OpPhi %int %37 %12 %27 %23
+ %30 = OpSLessThan %bool %38 %int_1
+ OpSelectionMerge %32 None
+ OpBranchConditional %30 %31 %32
+ %31 = OpLabel
+ %34 = OpIAdd %int %39 %int_2
+ OpStore %a %34
+ OpBranch %32
+ %32 = OpLabel
+ %40 = OpPhi %int %39 %24 %34 %31
+ OpBranch %14
+ %14 = OpLabel
+ %36 = OpIAdd %int %38 %int_1
+ OpStore %i %36
+ OpBranch %11
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto run_test = [&text_head, &text_tail, this](
+ SpvOp opcode, const std::string& op1,
+ const std::string& op2,
+ const PeelTraceType& expected_peel_trace) {
+ BuildAndCheckTrace(text_head, text_tail, opcode, "%22", op1, op2,
+ expected_peel_trace, expected_peel_trace.size() + 1);
+ };
+
+ // Test LT
+ // Peel before by a factor of 3.
+ {
+ SCOPED_TRACE("Peel before iv < 3");
+
+ run_test(SpvOpSLessThan, "%38", "%int_3",
+ {{LoopPeelingPass::PeelDirection::kBefore, 3u}});
+ }
+ {
+ SCOPED_TRACE("Peel before 3 > iv");
+
+ run_test(SpvOpSGreaterThan, "%int_3", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 3u}});
+ }
+
+ // Peel after by a factor of 2.
+ {
+ SCOPED_TRACE("Peel after iv < 8");
+
+ run_test(SpvOpSLessThan, "%38", "%int_8",
+ {{LoopPeelingPass::PeelDirection::kAfter, 2u}});
+ }
+ {
+ SCOPED_TRACE("Peel after 8 > iv");
+
+ run_test(SpvOpSGreaterThan, "%int_8", "%38",
+ {{LoopPeelingPass::PeelDirection::kAfter, 2u}});
+ }
+
+ // Test GT
+ // Peel before by a factor of 2.
+ {
+ SCOPED_TRACE("Peel before iv > 2");
+
+ run_test(SpvOpSGreaterThan, "%38", "%int_2",
+ {{LoopPeelingPass::PeelDirection::kBefore, 2u}});
+ }
+ {
+ SCOPED_TRACE("Peel before 2 < iv");
+
+ run_test(SpvOpSLessThan, "%int_2", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 2u}});
+ }
+
+ // Peel after by a factor of 3.
+ {
+ SCOPED_TRACE("Peel after iv > 7");
+
+ run_test(SpvOpSGreaterThan, "%38", "%int_7",
+ {{LoopPeelingPass::PeelDirection::kAfter, 3u}});
+ }
+ {
+ SCOPED_TRACE("Peel after 7 < iv");
+
+ run_test(SpvOpSLessThan, "%int_7", "%38",
+ {{LoopPeelingPass::PeelDirection::kAfter, 3u}});
+ }
+
+ // Test LE
+ // Peel before by a factor of 2.
+ {
+ SCOPED_TRACE("Peel before iv <= 1");
+
+ run_test(SpvOpSLessThanEqual, "%38", "%int_1",
+ {{LoopPeelingPass::PeelDirection::kBefore, 2u}});
+ }
+ {
+ SCOPED_TRACE("Peel before 1 => iv");
+
+ run_test(SpvOpSGreaterThanEqual, "%int_1", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 2u}});
+ }
+
+ // Peel after by a factor of 2.
+ {
+ SCOPED_TRACE("Peel after iv <= 7");
+
+ run_test(SpvOpSLessThanEqual, "%38", "%int_7",
+ {{LoopPeelingPass::PeelDirection::kAfter, 2u}});
+ }
+ {
+ SCOPED_TRACE("Peel after 7 => iv");
+
+ run_test(SpvOpSGreaterThanEqual, "%int_7", "%38",
+ {{LoopPeelingPass::PeelDirection::kAfter, 2u}});
+ }
+
+ // Test GE
+ // Peel before by a factor of 2.
+ {
+ SCOPED_TRACE("Peel before iv >= 2");
+
+ run_test(SpvOpSGreaterThanEqual, "%38", "%int_2",
+ {{LoopPeelingPass::PeelDirection::kBefore, 2u}});
+ }
+ {
+ SCOPED_TRACE("Peel before 2 <= iv");
+
+ run_test(SpvOpSLessThanEqual, "%int_2", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 2u}});
+ }
+
+ // Peel after by a factor of 2.
+ {
+ SCOPED_TRACE("Peel after iv >= 8");
+
+ run_test(SpvOpSGreaterThanEqual, "%38", "%int_8",
+ {{LoopPeelingPass::PeelDirection::kAfter, 2u}});
+ }
+ {
+ SCOPED_TRACE("Peel after 8 <= iv");
+
+ run_test(SpvOpSLessThanEqual, "%int_8", "%38",
+ {{LoopPeelingPass::PeelDirection::kAfter, 2u}});
+ }
+ // Test EQ
+ // Peel before by a factor of 1.
+ {
+ SCOPED_TRACE("Peel before iv == 0");
+
+ run_test(SpvOpIEqual, "%38", "%int_0",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+ {
+ SCOPED_TRACE("Peel before 0 == iv");
+
+ run_test(SpvOpIEqual, "%int_0", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+
+ // Peel after by a factor of 1.
+ {
+ SCOPED_TRACE("Peel after iv == 9");
+
+ run_test(SpvOpIEqual, "%38", "%int_9",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+ {
+ SCOPED_TRACE("Peel after 9 == iv");
+
+ run_test(SpvOpIEqual, "%int_9", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+
+ // Test NE
+ // Peel before by a factor of 1.
+ {
+ SCOPED_TRACE("Peel before iv != 0");
+
+ run_test(SpvOpINotEqual, "%38", "%int_0",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+ {
+ SCOPED_TRACE("Peel before 0 != iv");
+
+ run_test(SpvOpINotEqual, "%int_0", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+
+ // Peel after by a factor of 1.
+ {
+ SCOPED_TRACE("Peel after iv != 9");
+
+ run_test(SpvOpINotEqual, "%38", "%int_9",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+ {
+ SCOPED_TRACE("Peel after 9 != iv");
+
+ run_test(SpvOpINotEqual, "%int_9", "%38",
+ {{LoopPeelingPass::PeelDirection::kBefore, 1u}});
+ }
+}
+
+/*
+Test are derivation of the following generated test from the following GLSL +
+--eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int a = 0;
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ if (i < 3) {
+ a += 2;
+ }
+ }
+ }
+}
+*/
+TEST_F(PeelingPassTest, PeelingNestedPass) {
+ const std::string text_head = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %a "a"
+ OpName %i "i"
+ OpName %j "j"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_7 = OpConstant %int 7
+ %int_3 = OpConstant %int 3
+ %int_2 = OpConstant %int 2
+ %int_1 = OpConstant %int 1
+ %43 = OpUndef %int
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %a = OpVariable %_ptr_Function_int Function
+ %i = OpVariable %_ptr_Function_int Function
+ %j = OpVariable %_ptr_Function_int Function
+ OpStore %a %int_0
+ OpStore %i %int_0
+ OpBranch %11
+ %11 = OpLabel
+ %41 = OpPhi %int %int_0 %5 %45 %14
+ %42 = OpPhi %int %int_0 %5 %40 %14
+ %44 = OpPhi %int %43 %5 %46 %14
+ OpLoopMerge %13 %14 None
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThan %bool %42 %int_10
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ OpStore %j %int_0
+ OpBranch %21
+ %21 = OpLabel
+ %45 = OpPhi %int %41 %12 %47 %24
+ %46 = OpPhi %int %int_0 %12 %38 %24
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %27 = OpSLessThan %bool %46 %int_10
+ OpBranchConditional %27 %22 %23
+ %22 = OpLabel
+ )";
+
+ const std::string text_tail = R"(
+ OpSelectionMerge %32 None
+ OpBranchConditional %30 %31 %32
+ %31 = OpLabel
+ %35 = OpIAdd %int %45 %int_2
+ OpStore %a %35
+ OpBranch %32
+ %32 = OpLabel
+ %47 = OpPhi %int %45 %22 %35 %31
+ OpBranch %24
+ %24 = OpLabel
+ %38 = OpIAdd %int %46 %int_1
+ OpStore %j %38
+ OpBranch %21
+ %23 = OpLabel
+ OpBranch %14
+ %14 = OpLabel
+ %40 = OpIAdd %int %42 %int_1
+ OpStore %i %40
+ OpBranch %11
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto run_test =
+ [&text_head, &text_tail, this](
+ SpvOp opcode, const std::string& op1, const std::string& op2,
+ const PeelTraceType& expected_peel_trace, size_t nb_of_loops) {
+ BuildAndCheckTrace(text_head, text_tail, opcode, "%30", op1, op2,
+ expected_peel_trace, nb_of_loops);
+ };
+
+ // Peeling outer before by a factor of 3.
+ {
+ SCOPED_TRACE("Peel before iv_i < 3");
+
+ // Expect peel before by a factor of 3 and 4 loops at the end.
+ run_test(SpvOpSLessThan, "%42", "%int_3",
+ {{LoopPeelingPass::PeelDirection::kBefore, 3u}}, 4);
+ }
+ // Peeling outer loop after by a factor of 3.
+ {
+ SCOPED_TRACE("Peel after iv_i < 7");
+
+ // Expect peel after by a factor of 3 and 4 loops at the end.
+ run_test(SpvOpSLessThan, "%42", "%int_7",
+ {{LoopPeelingPass::PeelDirection::kAfter, 3u}}, 4);
+ }
+
+ // Peeling inner loop before by a factor of 3.
+ {
+ SCOPED_TRACE("Peel before iv_j < 3");
+
+ // Expect peel before by a factor of 3 and 3 loops at the end.
+ run_test(SpvOpSLessThan, "%46", "%int_3",
+ {{LoopPeelingPass::PeelDirection::kBefore, 3u}}, 3);
+ }
+ // Peeling inner loop after by a factor of 3.
+ {
+ SCOPED_TRACE("Peel after iv_j < 7");
+
+ // Expect peel after by a factor of 3 and 3 loops at the end.
+ run_test(SpvOpSLessThan, "%46", "%int_7",
+ {{LoopPeelingPass::PeelDirection::kAfter, 3u}}, 3);
+ }
+
+ // Not unworkable condition.
+ {
+ SCOPED_TRACE("No peel");
+
+ // Expect no peeling and 2 loops at the end.
+ run_test(SpvOpSLessThan, "%46", "%42", {}, 2);
+ }
+
+ // Could do a peeling of 3, but the goes over the threshold.
+ {
+ SCOPED_TRACE("Over threshold");
+
+ size_t current_threshold = LoopPeelingPass::GetLoopPeelingThreshold();
+ LoopPeelingPass::SetLoopPeelingThreshold(1u);
+ // Expect no peeling and 2 loops at the end.
+ run_test(SpvOpSLessThan, "%46", "%int_7", {}, 2);
+ LoopPeelingPass::SetLoopPeelingThreshold(current_threshold);
+ }
+}
+/*
+Test are derivation of the following generated test from the following GLSL +
+--eliminate-local-multi-store
+
+#version 330 core
+void main() {
+ int a = 0;
+ for (int i = 0, j = 0; i < 10; j++, i++) {
+ if (i < j) {
+ a += 2;
+ }
+ }
+}
+*/
+TEST_F(PeelingPassTest, PeelingNoChanges) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %a "a"
+ OpName %i "i"
+ OpName %j "j"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_2 = OpConstant %int 2
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %a = OpVariable %_ptr_Function_int Function
+ %i = OpVariable %_ptr_Function_int Function
+ %j = OpVariable %_ptr_Function_int Function
+ OpStore %a %int_0
+ OpStore %i %int_0
+ OpStore %j %int_0
+ OpBranch %12
+ %12 = OpLabel
+ %34 = OpPhi %int %int_0 %5 %37 %15
+ %35 = OpPhi %int %int_0 %5 %33 %15
+ %36 = OpPhi %int %int_0 %5 %31 %15
+ OpLoopMerge %14 %15 None
+ OpBranch %16
+ %16 = OpLabel
+ %20 = OpSLessThan %bool %35 %int_10
+ OpBranchConditional %20 %13 %14
+ %13 = OpLabel
+ %23 = OpSLessThan %bool %35 %36
+ OpSelectionMerge %25 None
+ OpBranchConditional %23 %24 %25
+ %24 = OpLabel
+ %28 = OpIAdd %int %34 %int_2
+ OpStore %a %28
+ OpBranch %25
+ %25 = OpLabel
+ %37 = OpPhi %int %34 %13 %28 %24
+ OpBranch %15
+ %15 = OpLabel
+ %31 = OpIAdd %int %36 %int_1
+ OpStore %j %31
+ %33 = OpIAdd %int %35 %int_1
+ OpStore %i %33
+ OpBranch %12
+ %14 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ {
+ auto result =
+ SinglePassRunAndDisassemble<LoopPeelingPass>(text, true, false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/unroll_assumptions.cpp b/test/opt/loop_optimizations/unroll_assumptions.cpp
new file mode 100644
index 0000000..62f77d7
--- /dev/null
+++ b/test/opt/loop_optimizations/unroll_assumptions.cpp
@@ -0,0 +1,1448 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/loop_unroller.h"
+#include "source/opt/loop_utils.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+template <int factor>
+class PartialUnrollerTestPass : public Pass {
+ public:
+ PartialUnrollerTestPass() : Pass() {}
+
+ const char* name() const override { return "Loop unroller"; }
+
+ Status Process() override {
+ bool changed = false;
+ for (Function& f : *context()->module()) {
+ LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(&f);
+ for (auto& loop : loop_descriptor) {
+ LoopUtils loop_utils{context(), &loop};
+ if (loop_utils.PartiallyUnroll(factor)) {
+ changed = true;
+ }
+ }
+ }
+
+ if (changed) return Pass::Status::SuccessWithChange;
+ return Pass::Status::SuccessWithoutChange;
+ }
+};
+
+/*
+Generated from the following GLSL
+#version 410 core
+layout(location = 0) flat in int in_upper_bound;
+void main() {
+ for (int i = 0; i < in_upper_bound; ++i) {
+ x[i] = 1.0f;
+ }
+}
+*/
+TEST_F(PassClassTest, CheckUpperBound) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "in_upper_bound"
+OpName %4 "x"
+OpDecorate %3 Flat
+OpDecorate %3 Location 0
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpTypeInt 32 1
+%8 = OpTypePointer Function %7
+%9 = OpConstant %7 0
+%10 = OpTypePointer Input %7
+%3 = OpVariable %10 Input
+%11 = OpTypeBool
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 10
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpConstant %12 1
+%18 = OpTypePointer Function %12
+%19 = OpConstant %7 1
+%2 = OpFunction %5 None %6
+%20 = OpLabel
+%4 = OpVariable %16 Function
+OpBranch %21
+%21 = OpLabel
+%22 = OpPhi %7 %9 %20 %23 %24
+OpLoopMerge %25 %24 Unroll
+OpBranch %26
+%26 = OpLabel
+%27 = OpLoad %7 %3
+%28 = OpSLessThan %11 %22 %27
+OpBranchConditional %28 %29 %25
+%29 = OpLabel
+%30 = OpAccessChain %18 %4 %22
+OpStore %30 %17
+OpBranch %24
+%24 = OpLabel
+%23 = OpIAdd %7 %22 %19
+OpBranch %21
+%25 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[10];
+ for (uint i = 0; i < 2; i++) {
+ for (float x = 0; x < 5; ++x) {
+ out_array[x + i*5] = i;
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, UnrollNestedLoopsInvalid) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 0
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpConstant %6 2
+%10 = OpTypeBool
+%11 = OpTypeInt 32 1
+%12 = OpTypePointer Function %11
+%13 = OpConstant %11 0
+%14 = OpConstant %11 5
+%15 = OpTypeFloat 32
+%16 = OpConstant %6 10
+%17 = OpTypeArray %15 %16
+%18 = OpTypePointer Function %17
+%19 = OpConstant %6 5
+%20 = OpTypePointer Function %15
+%21 = OpConstant %11 1
+%22 = OpUndef %11
+%2 = OpFunction %4 None %5
+%23 = OpLabel
+%3 = OpVariable %18 Function
+OpBranch %24
+%24 = OpLabel
+%25 = OpPhi %6 %8 %23 %26 %27
+%28 = OpPhi %11 %22 %23 %29 %27
+OpLoopMerge %30 %27 Unroll
+OpBranch %31
+%31 = OpLabel
+%32 = OpULessThan %10 %25 %9
+OpBranchConditional %32 %33 %30
+%33 = OpLabel
+OpBranch %34
+%34 = OpLabel
+%29 = OpPhi %11 %13 %33 %35 %36
+OpLoopMerge %37 %36 None
+OpBranch %38
+%38 = OpLabel
+%39 = OpSLessThan %10 %29 %14
+OpBranchConditional %39 %40 %37
+%40 = OpLabel
+%41 = OpBitcast %6 %29
+%42 = OpIMul %6 %25 %19
+%43 = OpIAdd %6 %41 %42
+%44 = OpConvertUToF %15 %25
+%45 = OpAccessChain %20 %3 %43
+OpStore %45 %44
+OpBranch %36
+%36 = OpLabel
+%35 = OpIAdd %11 %29 %21
+OpBranch %34
+%37 = OpLabel
+OpBranch %27
+%27 = OpLabel
+%26 = OpIAdd %6 %25 %21
+OpBranch %24
+%30 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main(){
+ float x[10];
+ for (int i = 0; i < 10; i++) {
+ if (i == 5) {
+ break;
+ }
+ x[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, BreakInBody) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+OpName %3 "x"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpConstant %6 10
+%10 = OpTypeBool
+%11 = OpConstant %6 5
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 10
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpTypePointer Function %12
+%18 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%19 = OpLabel
+%3 = OpVariable %16 Function
+OpBranch %20
+%20 = OpLabel
+%21 = OpPhi %6 %8 %19 %22 %23
+OpLoopMerge %24 %23 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %10 %21 %9
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%28 = OpIEqual %10 %21 %11
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+OpBranch %24
+%29 = OpLabel
+%31 = OpConvertSToF %12 %21
+%32 = OpAccessChain %17 %3 %21
+OpStore %32 %31
+OpBranch %23
+%23 = OpLabel
+%22 = OpIAdd %6 %21 %18
+OpBranch %20
+%24 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main(){
+ float x[10];
+ for (int i = 0; i < 10; i++) {
+ if (i == 5) {
+ continue;
+ }
+ x[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, ContinueInBody) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+OpName %3 "x"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpConstant %6 10
+%10 = OpTypeBool
+%11 = OpConstant %6 5
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 10
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpTypePointer Function %12
+%18 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%19 = OpLabel
+%3 = OpVariable %16 Function
+OpBranch %20
+%20 = OpLabel
+%21 = OpPhi %6 %8 %19 %22 %23
+OpLoopMerge %24 %23 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %10 %21 %9
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%28 = OpIEqual %10 %21 %11
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+OpBranch %23
+%29 = OpLabel
+%31 = OpConvertSToF %12 %21
+%32 = OpAccessChain %17 %3 %21
+OpStore %32 %31
+OpBranch %23
+%23 = OpLabel
+%22 = OpIAdd %6 %21 %18
+OpBranch %20
+%24 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main(){
+ float x[10];
+ for (int i = 0; i < 10; i++) {
+ if (i == 5) {
+ return;
+ }
+ x[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, ReturnInBody) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+OpName %3 "x"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpConstant %6 10
+%10 = OpTypeBool
+%11 = OpConstant %6 5
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 10
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpTypePointer Function %12
+%18 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%19 = OpLabel
+%3 = OpVariable %16 Function
+OpBranch %20
+%20 = OpLabel
+%21 = OpPhi %6 %8 %19 %22 %23
+OpLoopMerge %24 %23 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %10 %21 %9
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%28 = OpIEqual %10 %21 %11
+OpSelectionMerge %29 None
+OpBranchConditional %28 %30 %29
+%30 = OpLabel
+OpReturn
+%29 = OpLabel
+%31 = OpConvertSToF %12 %21
+%32 = OpAccessChain %17 %3 %21
+OpStore %32 %31
+OpBranch %23
+%23 = OpLabel
+%22 = OpIAdd %6 %21 %18
+OpBranch %20
+%24 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main() {
+ int j = 0;
+ for (int i = 0; i < 10 && i > 0; i++) {
+ j++;
+ }
+}
+*/
+TEST_F(PassClassTest, MultipleConditionsSingleVariable) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpTypePointer Function %5
+%7 = OpConstant %5 0
+%8 = OpConstant %5 10
+%9 = OpTypeBool
+%10 = OpConstant %5 1
+%2 = OpFunction %3 None %4
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %5 %7 %11 %14 %15
+%16 = OpPhi %5 %7 %11 %17 %15
+OpLoopMerge %18 %15 Unroll
+OpBranch %19
+%19 = OpLabel
+%20 = OpSLessThan %9 %16 %8
+%21 = OpSGreaterThan %9 %16 %7
+%22 = OpLogicalAnd %9 %20 %21
+OpBranchConditional %22 %23 %18
+%23 = OpLabel
+%14 = OpIAdd %5 %13 %10
+OpBranch %15
+%15 = OpLabel
+%17 = OpIAdd %5 %16 %10
+OpBranch %12
+%18 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main() {
+ int i = 0;
+ int j = 0;
+ int k = 0;
+ for (; i < 10 && j > 0; i++, j++) {
+ k++;
+ }
+}
+*/
+TEST_F(PassClassTest, MultipleConditionsMultipleVariables) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpTypePointer Function %5
+%7 = OpConstant %5 0
+%8 = OpConstant %5 10
+%9 = OpTypeBool
+%10 = OpConstant %5 1
+%2 = OpFunction %3 None %4
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %5 %7 %11 %14 %15
+%16 = OpPhi %5 %7 %11 %17 %15
+%18 = OpPhi %5 %7 %11 %19 %15
+OpLoopMerge %20 %15 Unroll
+OpBranch %21
+%21 = OpLabel
+%22 = OpSLessThan %9 %13 %8
+%23 = OpSGreaterThan %9 %16 %7
+%24 = OpLogicalAnd %9 %22 %23
+OpBranchConditional %24 %25 %20
+%25 = OpLabel
+%19 = OpIAdd %5 %18 %10
+OpBranch %15
+%15 = OpLabel
+%14 = OpIAdd %5 %13 %10
+%17 = OpIAdd %5 %16 %10
+OpBranch %12
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main() {
+ float i = 0.0;
+ int j = 0;
+ for (; i < 10; i++) {
+ j++;
+ }
+}
+*/
+TEST_F(PassClassTest, FloatingPointLoop) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeFloat 32
+%6 = OpTypePointer Function %5
+%7 = OpConstant %5 0
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 0
+%11 = OpConstant %5 10
+%12 = OpTypeBool
+%13 = OpConstant %8 1
+%14 = OpConstant %5 1
+%2 = OpFunction %3 None %4
+%15 = OpLabel
+OpBranch %16
+%16 = OpLabel
+%17 = OpPhi %5 %7 %15 %18 %19
+%20 = OpPhi %8 %10 %15 %21 %19
+OpLoopMerge %22 %19 Unroll
+OpBranch %23
+%23 = OpLabel
+%24 = OpFOrdLessThan %12 %17 %11
+OpBranchConditional %24 %25 %22
+%25 = OpLabel
+%21 = OpIAdd %8 %20 %13
+OpBranch %19
+%19 = OpLabel
+%18 = OpFAdd %5 %17 %14
+OpBranch %16
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main() {
+ int i = 2;
+ int j = 0;
+ if (j == 0) { i = 5; }
+ for (; i < 3; ++i) {
+ j++;
+ }
+}
+*/
+TEST_F(PassClassTest, InductionPhiOutsideLoop) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpTypePointer Function %5
+%7 = OpConstant %5 2
+%8 = OpConstant %5 0
+%9 = OpTypeBool
+%10 = OpConstant %5 5
+%11 = OpConstant %5 3
+%12 = OpConstant %5 1
+%2 = OpFunction %3 None %4
+%13 = OpLabel
+%14 = OpIEqual %9 %8 %8
+OpSelectionMerge %15 None
+OpBranchConditional %14 %16 %15
+%16 = OpLabel
+OpBranch %15
+%15 = OpLabel
+%17 = OpPhi %5 %7 %13 %10 %16
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %5 %17 %15 %20 %21
+%22 = OpPhi %5 %8 %15 %23 %21
+OpLoopMerge %24 %21 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %9 %19 %11
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%23 = OpIAdd %5 %22 %12
+OpBranch %21
+%21 = OpLabel
+%20 = OpIAdd %5 %19 %12
+OpBranch %18
+%24 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main() {
+ int j = 0;
+ for (int i = 0; i == 0; ++i) {
+ ++j;
+ }
+ for (int i = 0; i != 3; ++i) {
+ ++j;
+ }
+ for (int i = 0; i < 3; i *= 2) {
+ ++j;
+ }
+ for (int i = 10; i > 3; i /= 2) {
+ ++j;
+ }
+ for (int i = 10; i > 3; i |= 2) {
+ ++j;
+ }
+ for (int i = 10; i > 3; i &= 2) {
+ ++j;
+ }
+ for (int i = 10; i > 3; i ^= 2) {
+ ++j;
+ }
+ for (int i = 0; i < 3; i << 2) {
+ ++j;
+ }
+ for (int i = 10; i > 3; i >> 2) {
+ ++j;
+ }
+}
+*/
+TEST_F(PassClassTest, UnsupportedLoopTypes) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpTypePointer Function %5
+%7 = OpConstant %5 0
+%8 = OpTypeBool
+%9 = OpConstant %5 1
+%10 = OpConstant %5 3
+%11 = OpConstant %5 2
+%12 = OpConstant %5 10
+%2 = OpFunction %3 None %4
+%13 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%15 = OpPhi %5 %7 %13 %16 %17
+%18 = OpPhi %5 %7 %13 %19 %17
+OpLoopMerge %20 %17 Unroll
+OpBranch %21
+%21 = OpLabel
+%22 = OpIEqual %8 %18 %7
+OpBranchConditional %22 %23 %20
+%23 = OpLabel
+%16 = OpIAdd %5 %15 %9
+OpBranch %17
+%17 = OpLabel
+%19 = OpIAdd %5 %18 %9
+OpBranch %14
+%20 = OpLabel
+OpBranch %24
+%24 = OpLabel
+%25 = OpPhi %5 %15 %20 %26 %27
+%28 = OpPhi %5 %7 %20 %29 %27
+OpLoopMerge %30 %27 Unroll
+OpBranch %31
+%31 = OpLabel
+%32 = OpINotEqual %8 %28 %10
+OpBranchConditional %32 %33 %30
+%33 = OpLabel
+%26 = OpIAdd %5 %25 %9
+OpBranch %27
+%27 = OpLabel
+%29 = OpIAdd %5 %28 %9
+OpBranch %24
+%30 = OpLabel
+OpBranch %34
+%34 = OpLabel
+%35 = OpPhi %5 %25 %30 %36 %37
+%38 = OpPhi %5 %7 %30 %39 %37
+OpLoopMerge %40 %37 Unroll
+OpBranch %41
+%41 = OpLabel
+%42 = OpSLessThan %8 %38 %10
+OpBranchConditional %42 %43 %40
+%43 = OpLabel
+%36 = OpIAdd %5 %35 %9
+OpBranch %37
+%37 = OpLabel
+%39 = OpIMul %5 %38 %11
+OpBranch %34
+%40 = OpLabel
+OpBranch %44
+%44 = OpLabel
+%45 = OpPhi %5 %35 %40 %46 %47
+%48 = OpPhi %5 %12 %40 %49 %47
+OpLoopMerge %50 %47 Unroll
+OpBranch %51
+%51 = OpLabel
+%52 = OpSGreaterThan %8 %48 %10
+OpBranchConditional %52 %53 %50
+%53 = OpLabel
+%46 = OpIAdd %5 %45 %9
+OpBranch %47
+%47 = OpLabel
+%49 = OpSDiv %5 %48 %11
+OpBranch %44
+%50 = OpLabel
+OpBranch %54
+%54 = OpLabel
+%55 = OpPhi %5 %45 %50 %56 %57
+%58 = OpPhi %5 %12 %50 %59 %57
+OpLoopMerge %60 %57 Unroll
+OpBranch %61
+%61 = OpLabel
+%62 = OpSGreaterThan %8 %58 %10
+OpBranchConditional %62 %63 %60
+%63 = OpLabel
+%56 = OpIAdd %5 %55 %9
+OpBranch %57
+%57 = OpLabel
+%59 = OpBitwiseOr %5 %58 %11
+OpBranch %54
+%60 = OpLabel
+OpBranch %64
+%64 = OpLabel
+%65 = OpPhi %5 %55 %60 %66 %67
+%68 = OpPhi %5 %12 %60 %69 %67
+OpLoopMerge %70 %67 Unroll
+OpBranch %71
+%71 = OpLabel
+%72 = OpSGreaterThan %8 %68 %10
+OpBranchConditional %72 %73 %70
+%73 = OpLabel
+%66 = OpIAdd %5 %65 %9
+OpBranch %67
+%67 = OpLabel
+%69 = OpBitwiseAnd %5 %68 %11
+OpBranch %64
+%70 = OpLabel
+OpBranch %74
+%74 = OpLabel
+%75 = OpPhi %5 %65 %70 %76 %77
+%78 = OpPhi %5 %12 %70 %79 %77
+OpLoopMerge %80 %77 Unroll
+OpBranch %81
+%81 = OpLabel
+%82 = OpSGreaterThan %8 %78 %10
+OpBranchConditional %82 %83 %80
+%83 = OpLabel
+%76 = OpIAdd %5 %75 %9
+OpBranch %77
+%77 = OpLabel
+%79 = OpBitwiseXor %5 %78 %11
+OpBranch %74
+%80 = OpLabel
+OpBranch %84
+%84 = OpLabel
+%85 = OpPhi %5 %75 %80 %86 %87
+OpLoopMerge %88 %87 Unroll
+OpBranch %89
+%89 = OpLabel
+%90 = OpSLessThan %8 %7 %10
+OpBranchConditional %90 %91 %88
+%91 = OpLabel
+%86 = OpIAdd %5 %85 %9
+OpBranch %87
+%87 = OpLabel
+%92 = OpShiftLeftLogical %5 %7 %11
+OpBranch %84
+%88 = OpLabel
+OpBranch %93
+%93 = OpLabel
+%94 = OpPhi %5 %85 %88 %95 %96
+OpLoopMerge %97 %96 Unroll
+OpBranch %98
+%98 = OpLabel
+%99 = OpSGreaterThan %8 %12 %10
+OpBranchConditional %99 %100 %97
+%100 = OpLabel
+%95 = OpIAdd %5 %94 %9
+OpBranch %96
+%96 = OpLabel
+%101 = OpShiftRightArithmetic %5 %12 %11
+OpBranch %93
+%97 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+#version 430
+
+layout(location = 0) out float o;
+
+void main(void) {
+ for (int j = 2; j < 0; j += 1) {
+ o += 1.0;
+ }
+}
+*/
+TEST_F(PassClassTest, NegativeNumberOfIterations) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "o"
+OpDecorate %3 Location 0
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 2
+%9 = OpConstant %6 0
+%10 = OpTypeBool
+%11 = OpTypeFloat 32
+%12 = OpTypePointer Output %11
+%3 = OpVariable %12 Output
+%13 = OpConstant %11 1
+%14 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%15 = OpLabel
+OpBranch %16
+%16 = OpLabel
+%17 = OpPhi %6 %8 %15 %18 %19
+OpLoopMerge %20 %19 None
+OpBranch %21
+%21 = OpLabel
+%22 = OpSLessThan %10 %17 %9
+OpBranchConditional %22 %23 %20
+%23 = OpLabel
+%24 = OpLoad %11 %3
+%25 = OpFAdd %11 %24 %13
+OpStore %3 %25
+OpBranch %19
+%19 = OpLabel
+%18 = OpIAdd %6 %17 %14
+OpBranch %16
+%20 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+#version 430
+
+layout(location = 0) out float o;
+
+void main(void) {
+ float s = 0.0;
+ for (int j = 0; j < 3; j += 1) {
+ s += 1.0;
+ j += 1;
+ }
+ o = s;
+}
+*/
+TEST_F(PassClassTest, MultipleStepOperations) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "o"
+OpDecorate %3 Location 0
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeFloat 32
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 0
+%12 = OpConstant %9 3
+%13 = OpTypeBool
+%14 = OpConstant %6 1
+%15 = OpConstant %9 1
+%16 = OpTypePointer Output %6
+%3 = OpVariable %16 Output
+%2 = OpFunction %4 None %5
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %6 %8 %17 %20 %21
+%22 = OpPhi %9 %11 %17 %23 %21
+OpLoopMerge %24 %21 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %13 %22 %12
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%20 = OpFAdd %6 %19 %14
+%28 = OpIAdd %9 %22 %15
+OpBranch %21
+%21 = OpLabel
+%23 = OpIAdd %9 %28 %15
+OpBranch %18
+%24 = OpLabel
+OpStore %3 %19
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+#version 430
+
+layout(location = 0) out float o;
+
+void main(void) {
+ float s = 0.0;
+ for (int j = 10; j > 20; j -= 1) {
+ s += 1.0;
+ }
+ o = s;
+}
+*/
+
+TEST_F(PassClassTest, ConditionFalseFromStartGreaterThan) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "o"
+OpDecorate %3 Location 0
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeFloat 32
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 10
+%12 = OpConstant %9 20
+%13 = OpTypeBool
+%14 = OpConstant %6 1
+%15 = OpConstant %9 1
+%16 = OpTypePointer Output %6
+%3 = OpVariable %16 Output
+%2 = OpFunction %4 None %5
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %6 %8 %17 %20 %21
+%22 = OpPhi %9 %11 %17 %23 %21
+OpLoopMerge %24 %21 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSGreaterThan %13 %22 %12
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%20 = OpFAdd %6 %19 %14
+OpBranch %21
+%21 = OpLabel
+%23 = OpISub %9 %22 %15
+OpBranch %18
+%24 = OpLabel
+OpStore %3 %19
+OpReturn
+OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+#version 430
+
+layout(location = 0) out float o;
+
+void main(void) {
+ float s = 0.0;
+ for (int j = 10; j >= 20; j -= 1) {
+ s += 1.0;
+ }
+ o = s;
+}
+*/
+TEST_F(PassClassTest, ConditionFalseFromStartGreaterThanOrEqual) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "o"
+OpDecorate %3 Location 0
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeFloat 32
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 10
+%12 = OpConstant %9 20
+%13 = OpTypeBool
+%14 = OpConstant %6 1
+%15 = OpConstant %9 1
+%16 = OpTypePointer Output %6
+%3 = OpVariable %16 Output
+%2 = OpFunction %4 None %5
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %6 %8 %17 %20 %21
+%22 = OpPhi %9 %11 %17 %23 %21
+OpLoopMerge %24 %21 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSGreaterThanEqual %13 %22 %12
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%20 = OpFAdd %6 %19 %14
+OpBranch %21
+%21 = OpLabel
+%23 = OpISub %9 %22 %15
+OpBranch %18
+%24 = OpLabel
+OpStore %3 %19
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+#version 430
+
+layout(location = 0) out float o;
+
+void main(void) {
+ float s = 0.0;
+ for (int j = 20; j < 10; j -= 1) {
+ s += 1.0;
+ }
+ o = s;
+}
+*/
+TEST_F(PassClassTest, ConditionFalseFromStartLessThan) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "o"
+OpDecorate %3 Location 0
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeFloat 32
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 20
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpConstant %6 1
+%15 = OpConstant %9 1
+%16 = OpTypePointer Output %6
+%3 = OpVariable %16 Output
+%2 = OpFunction %4 None %5
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %6 %8 %17 %20 %21
+%22 = OpPhi %9 %11 %17 %23 %21
+OpLoopMerge %24 %21 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThan %13 %22 %12
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%20 = OpFAdd %6 %19 %14
+OpBranch %21
+%21 = OpLabel
+%23 = OpISub %9 %22 %15
+OpBranch %18
+%24 = OpLabel
+OpStore %3 %19
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+/*
+#version 430
+
+layout(location = 0) out float o;
+
+void main(void) {
+ float s = 0.0;
+ for (int j = 20; j <= 10; j -= 1) {
+ s += 1.0;
+ }
+ o = s;
+}
+*/
+TEST_F(PassClassTest, ConditionFalseFromStartLessThanEqual) {
+ // clang-format off
+ // With LocalMultiStoreElimPass
+const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "o"
+OpDecorate %3 Location 0
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeFloat 32
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpTypeInt 32 1
+%10 = OpTypePointer Function %9
+%11 = OpConstant %9 20
+%12 = OpConstant %9 10
+%13 = OpTypeBool
+%14 = OpConstant %6 1
+%15 = OpConstant %9 1
+%16 = OpTypePointer Output %6
+%3 = OpVariable %16 Output
+%2 = OpFunction %4 None %5
+%17 = OpLabel
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %6 %8 %17 %20 %21
+%22 = OpPhi %9 %11 %17 %23 %21
+OpLoopMerge %24 %21 Unroll
+OpBranch %25
+%25 = OpLabel
+%26 = OpSLessThanEqual %13 %22 %12
+OpBranchConditional %26 %27 %24
+%27 = OpLabel
+%20 = OpFAdd %6 %19 %14
+OpBranch %21
+%21 = OpLabel
+%23 = OpISub %9 %22 %15
+OpBranch %18
+%24 = OpLabel
+OpStore %3 %19
+OpReturn
+OpFunctionEnd
+)";
+
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ // Make sure the pass doesn't run
+ SinglePassRunAndCheck<LoopUnroller>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<1>>(text, text, false);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, text, false);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/unroll_simple.cpp b/test/opt/loop_optimizations/unroll_simple.cpp
new file mode 100644
index 0000000..f551e7c
--- /dev/null
+++ b/test/opt/loop_optimizations/unroll_simple.cpp
@@ -0,0 +1,3003 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/loop_unroller.h"
+#include "source/opt/loop_utils.h"
+#include "source/opt/pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ float x[4];
+ for (int i = 0; i < 4; ++i) {
+ x[i] = 1.0f;
+ }
+}
+*/
+TEST_F(PassClassTest, SimpleFullyUnrollTest) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %5 "x"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 4
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 4
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Function %16
+ %18 = OpConstant %13 1
+ %19 = OpTypePointer Function %13
+ %20 = OpConstant %8 1
+ %21 = OpTypeVector %13 4
+ %22 = OpTypePointer Output %21
+ %3 = OpVariable %22 Output
+ %2 = OpFunction %6 None %7
+ %23 = OpLabel
+ %5 = OpVariable %17 Function
+ OpBranch %24
+ %24 = OpLabel
+ %35 = OpPhi %8 %10 %23 %34 %26
+ OpLoopMerge %25 %26 Unroll
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %12 %35 %11
+ OpBranchConditional %29 %30 %25
+ %30 = OpLabel
+ %32 = OpAccessChain %19 %5 %35
+ OpStore %32 %18
+ OpBranch %26
+ %26 = OpLabel
+ %34 = OpIAdd %8 %35 %20
+ OpBranch %24
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 330
+OpName %2 "main"
+OpName %4 "x"
+OpName %3 "c"
+OpDecorate %3 Location 0
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpTypeInt 32 1
+%8 = OpTypePointer Function %7
+%9 = OpConstant %7 0
+%10 = OpConstant %7 4
+%11 = OpTypeBool
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 4
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpConstant %12 1
+%18 = OpTypePointer Function %12
+%19 = OpConstant %7 1
+%20 = OpTypeVector %12 4
+%21 = OpTypePointer Output %20
+%3 = OpVariable %21 Output
+%2 = OpFunction %5 None %6
+%22 = OpLabel
+%4 = OpVariable %16 Function
+OpBranch %23
+%23 = OpLabel
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %11 %9 %10
+OpBranch %30
+%30 = OpLabel
+%31 = OpAccessChain %18 %4 %9
+OpStore %31 %17
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %7 %9 %19
+OpBranch %32
+%32 = OpLabel
+OpBranch %34
+%34 = OpLabel
+%35 = OpSLessThan %11 %25 %10
+OpBranch %36
+%36 = OpLabel
+%37 = OpAccessChain %18 %4 %25
+OpStore %37 %17
+OpBranch %38
+%38 = OpLabel
+%39 = OpIAdd %7 %25 %19
+OpBranch %40
+%40 = OpLabel
+OpBranch %42
+%42 = OpLabel
+%43 = OpSLessThan %11 %39 %10
+OpBranch %44
+%44 = OpLabel
+%45 = OpAccessChain %18 %4 %39
+OpStore %45 %17
+OpBranch %46
+%46 = OpLabel
+%47 = OpIAdd %7 %39 %19
+OpBranch %48
+%48 = OpLabel
+OpBranch %50
+%50 = OpLabel
+%51 = OpSLessThan %11 %47 %10
+OpBranch %52
+%52 = OpLabel
+%53 = OpAccessChain %18 %4 %47
+OpStore %53 %17
+OpBranch %54
+%54 = OpLabel
+%55 = OpIAdd %7 %47 %19
+OpBranch %27
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, output, false);
+}
+
+template <int factor>
+class PartialUnrollerTestPass : public Pass {
+ public:
+ PartialUnrollerTestPass() : Pass() {}
+
+ const char* name() const override { return "Loop unroller"; }
+
+ Status Process() override {
+ for (Function& f : *context()->module()) {
+ LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(&f);
+ for (auto& loop : loop_descriptor) {
+ LoopUtils loop_utils{context(), &loop};
+ loop_utils.PartiallyUnroll(factor);
+ }
+ }
+
+ return Pass::Status::SuccessWithChange;
+ }
+};
+
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ float x[10];
+ for (int i = 0; i < 10; ++i) {
+ x[i] = 1.0f;
+ }
+}
+*/
+TEST_F(PassClassTest, SimplePartialUnroll) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %5 "x"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 10
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Function %16
+ %18 = OpConstant %13 1
+ %19 = OpTypePointer Function %13
+ %20 = OpConstant %8 1
+ %21 = OpTypeVector %13 4
+ %22 = OpTypePointer Output %21
+ %3 = OpVariable %22 Output
+ %2 = OpFunction %6 None %7
+ %23 = OpLabel
+ %5 = OpVariable %17 Function
+ OpBranch %24
+ %24 = OpLabel
+ %35 = OpPhi %8 %10 %23 %34 %26
+ OpLoopMerge %25 %26 Unroll
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %12 %35 %11
+ OpBranchConditional %29 %30 %25
+ %30 = OpLabel
+ %32 = OpAccessChain %19 %5 %35
+ OpStore %32 %18
+ OpBranch %26
+ %26 = OpLabel
+ %34 = OpIAdd %8 %35 %20
+ OpBranch %24
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 330
+OpName %2 "main"
+OpName %4 "x"
+OpName %3 "c"
+OpDecorate %3 Location 0
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpTypeInt 32 1
+%8 = OpTypePointer Function %7
+%9 = OpConstant %7 0
+%10 = OpConstant %7 10
+%11 = OpTypeBool
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 10
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpConstant %12 1
+%18 = OpTypePointer Function %12
+%19 = OpConstant %7 1
+%20 = OpTypeVector %12 4
+%21 = OpTypePointer Output %20
+%3 = OpVariable %21 Output
+%2 = OpFunction %5 None %6
+%22 = OpLabel
+%4 = OpVariable %16 Function
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %7 %9 %22 %39 %38
+OpLoopMerge %27 %38 DontUnroll
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %11 %24 %10
+OpBranchConditional %29 %30 %27
+%30 = OpLabel
+%31 = OpAccessChain %18 %4 %24
+OpStore %31 %17
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %7 %24 %19
+OpBranch %32
+%32 = OpLabel
+OpBranch %34
+%34 = OpLabel
+%35 = OpSLessThan %11 %25 %10
+OpBranch %36
+%36 = OpLabel
+%37 = OpAccessChain %18 %4 %25
+OpStore %37 %17
+OpBranch %38
+%38 = OpLabel
+%39 = OpIAdd %7 %25 %19
+OpBranch %23
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, output, false);
+}
+
+/*
+Generated from the following GLSL
+#version 330 core
+layout(location = 0) out vec4 c;
+void main() {
+ float x[10];
+ for (int i = 0; i < 10; ++i) {
+ x[i] = 1.0f;
+ }
+}
+*/
+TEST_F(PassClassTest, SimpleUnevenPartialUnroll) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 330
+ OpName %2 "main"
+ OpName %5 "x"
+ OpName %3 "c"
+ OpDecorate %3 Location 0
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 10
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Function %16
+ %18 = OpConstant %13 1
+ %19 = OpTypePointer Function %13
+ %20 = OpConstant %8 1
+ %21 = OpTypeVector %13 4
+ %22 = OpTypePointer Output %21
+ %3 = OpVariable %22 Output
+ %2 = OpFunction %6 None %7
+ %23 = OpLabel
+ %5 = OpVariable %17 Function
+ OpBranch %24
+ %24 = OpLabel
+ %35 = OpPhi %8 %10 %23 %34 %26
+ OpLoopMerge %25 %26 Unroll
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSLessThan %12 %35 %11
+ OpBranchConditional %29 %30 %25
+ %30 = OpLabel
+ %32 = OpAccessChain %19 %5 %35
+ OpStore %32 %18
+ OpBranch %26
+ %26 = OpLabel
+ %34 = OpIAdd %8 %35 %20
+ OpBranch %24
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 330
+OpName %2 "main"
+OpName %4 "x"
+OpName %3 "c"
+OpDecorate %3 Location 0
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpTypeInt 32 1
+%8 = OpTypePointer Function %7
+%9 = OpConstant %7 0
+%10 = OpConstant %7 10
+%11 = OpTypeBool
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 10
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpConstant %12 1
+%18 = OpTypePointer Function %12
+%19 = OpConstant %7 1
+%20 = OpTypeVector %12 4
+%21 = OpTypePointer Output %20
+%3 = OpVariable %21 Output
+%58 = OpConstant %13 1
+%2 = OpFunction %5 None %6
+%22 = OpLabel
+%4 = OpVariable %16 Function
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %7 %9 %22 %25 %26
+OpLoopMerge %32 %26 Unroll
+OpBranch %28
+%28 = OpLabel
+%29 = OpSLessThan %11 %24 %58
+OpBranchConditional %29 %30 %32
+%30 = OpLabel
+%31 = OpAccessChain %18 %4 %24
+OpStore %31 %17
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %7 %24 %19
+OpBranch %23
+%32 = OpLabel
+OpBranch %33
+%33 = OpLabel
+%34 = OpPhi %7 %24 %32 %57 %56
+OpLoopMerge %41 %56 DontUnroll
+OpBranch %35
+%35 = OpLabel
+%36 = OpSLessThan %11 %34 %10
+OpBranchConditional %36 %37 %41
+%37 = OpLabel
+%38 = OpAccessChain %18 %4 %34
+OpStore %38 %17
+OpBranch %39
+%39 = OpLabel
+%40 = OpIAdd %7 %34 %19
+OpBranch %42
+%42 = OpLabel
+OpBranch %44
+%44 = OpLabel
+%45 = OpSLessThan %11 %40 %10
+OpBranch %46
+%46 = OpLabel
+%47 = OpAccessChain %18 %4 %40
+OpStore %47 %17
+OpBranch %48
+%48 = OpLabel
+%49 = OpIAdd %7 %40 %19
+OpBranch %50
+%50 = OpLabel
+OpBranch %52
+%52 = OpLabel
+%53 = OpSLessThan %11 %49 %10
+OpBranch %54
+%54 = OpLabel
+%55 = OpAccessChain %18 %4 %49
+OpStore %55 %17
+OpBranch %56
+%56 = OpLabel
+%57 = OpIAdd %7 %49 %19
+OpBranch %33
+%41 = OpLabel
+OpReturn
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ // By unrolling by a factor that doesn't divide evenly into the number of loop
+ // iterations we perfom an additional transform when partially unrolling to
+ // account for the remainder.
+ SinglePassRunAndCheck<PartialUnrollerTestPass<3>>(text, output, false);
+}
+
+/* Generated from
+#version 410 core
+layout(location=0) flat in int upper_bound;
+void main() {
+ float x[10];
+ for (int i = 2; i < 8; i+=2) {
+ x[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, SimpleLoopIterationsCheck) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %5 "x"
+OpName %3 "upper_bound"
+OpDecorate %3 Flat
+OpDecorate %3 Location 0
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 2
+%11 = OpConstant %8 8
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpTypePointer Input %8
+%3 = OpVariable %19 Input
+%2 = OpFunction %6 None %7
+%20 = OpLabel
+%5 = OpVariable %17 Function
+OpBranch %21
+%21 = OpLabel
+%34 = OpPhi %8 %10 %20 %33 %23
+OpLoopMerge %22 %23 Unroll
+OpBranch %24
+%24 = OpLabel
+%26 = OpSLessThan %12 %34 %11
+OpBranchConditional %26 %27 %22
+%27 = OpLabel
+%30 = OpConvertSToF %13 %34
+%31 = OpAccessChain %18 %5 %34
+OpStore %31 %30
+OpBranch %23
+%23 = OpLabel
+%33 = OpIAdd %8 %34 %10
+OpBranch %21
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ Function* f = spvtest::GetFunction(module, 2);
+
+ LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f);
+ EXPECT_EQ(loop_descriptor.NumLoops(), 1u);
+
+ Loop& loop = loop_descriptor.GetLoopByIndex(0);
+
+ EXPECT_TRUE(loop.HasUnrollLoopControl());
+
+ BasicBlock* condition = loop.FindConditionBlock();
+ EXPECT_EQ(condition->id(), 24u);
+
+ Instruction* induction = loop.FindConditionVariable(condition);
+ EXPECT_EQ(induction->result_id(), 34u);
+
+ LoopUtils loop_utils{context.get(), &loop};
+ EXPECT_TRUE(loop_utils.CanPerformUnroll());
+
+ size_t iterations = 0;
+ EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(),
+ &iterations));
+ EXPECT_EQ(iterations, 3u);
+}
+
+/* Generated from
+#version 410 core
+void main() {
+ float x[10];
+ for (int i = -1; i < 6; i+=3) {
+ x[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, SimpleLoopIterationsCheckSignedInit) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main" %3
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %5 "x"
+OpName %3 "upper_bound"
+OpDecorate %3 Flat
+OpDecorate %3 Location 0
+%6 = OpTypeVoid
+%7 = OpTypeFunction %6
+%8 = OpTypeInt 32 1
+%9 = OpTypePointer Function %8
+%10 = OpConstant %8 -1
+%11 = OpConstant %8 6
+%12 = OpTypeBool
+%13 = OpTypeFloat 32
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 10
+%16 = OpTypeArray %13 %15
+%17 = OpTypePointer Function %16
+%18 = OpTypePointer Function %13
+%19 = OpConstant %8 3
+%20 = OpTypePointer Input %8
+%3 = OpVariable %20 Input
+%2 = OpFunction %6 None %7
+%21 = OpLabel
+%5 = OpVariable %17 Function
+OpBranch %22
+%22 = OpLabel
+%35 = OpPhi %8 %10 %21 %34 %24
+OpLoopMerge %23 %24 None
+OpBranch %25
+%25 = OpLabel
+%27 = OpSLessThan %12 %35 %11
+OpBranchConditional %27 %28 %23
+%28 = OpLabel
+%31 = OpConvertSToF %13 %35
+%32 = OpAccessChain %18 %5 %35
+OpStore %32 %31
+OpBranch %24
+%24 = OpLabel
+%34 = OpIAdd %8 %35 %19
+OpBranch %22
+%23 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ Function* f = spvtest::GetFunction(module, 2);
+
+ LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f);
+
+ EXPECT_EQ(loop_descriptor.NumLoops(), 1u);
+
+ Loop& loop = loop_descriptor.GetLoopByIndex(0);
+
+ EXPECT_FALSE(loop.HasUnrollLoopControl());
+
+ BasicBlock* condition = loop.FindConditionBlock();
+ EXPECT_EQ(condition->id(), 25u);
+
+ Instruction* induction = loop.FindConditionVariable(condition);
+ EXPECT_EQ(induction->result_id(), 35u);
+
+ LoopUtils loop_utils{context.get(), &loop};
+ EXPECT_TRUE(loop_utils.CanPerformUnroll());
+
+ size_t iterations = 0;
+ EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(),
+ &iterations));
+ EXPECT_EQ(iterations, 3u);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[6];
+ for (uint i = 0; i < 2; i++) {
+ for (int x = 0; x < 3; ++x) {
+ out_array[x + i*3] = i;
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, UnrollNestedLoops) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %35 "out_array"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 2
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 1
+ %20 = OpTypePointer Function %19
+ %22 = OpConstant %19 0
+ %29 = OpConstant %19 3
+ %31 = OpTypeFloat 32
+ %32 = OpConstant %6 6
+ %33 = OpTypeArray %31 %32
+ %34 = OpTypePointer Function %33
+ %39 = OpConstant %6 3
+ %44 = OpTypePointer Function %31
+ %47 = OpConstant %19 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %35 = OpVariable %34 Function
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %50 %13
+ OpLoopMerge %12 %13 Unroll
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpULessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %54 = OpPhi %19 %22 %11 %48 %26
+ OpLoopMerge %25 %26 Unroll
+ OpBranch %27
+ %27 = OpLabel
+ %30 = OpSLessThan %17 %54 %29
+ OpBranchConditional %30 %24 %25
+ %24 = OpLabel
+ %37 = OpBitcast %6 %54
+ %40 = OpIMul %6 %51 %39
+ %41 = OpIAdd %6 %37 %40
+ %43 = OpConvertUToF %31 %51
+ %45 = OpAccessChain %44 %35 %41
+ OpStore %45 %43
+ OpBranch %26
+ %26 = OpLabel
+ %48 = OpIAdd %19 %54 %47
+ OpBranch %23
+ %25 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %50 = OpIAdd %6 %51 %47
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 0
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpConstant %6 2
+%10 = OpTypeBool
+%11 = OpTypeInt 32 1
+%12 = OpTypePointer Function %11
+%13 = OpConstant %11 0
+%14 = OpConstant %11 3
+%15 = OpTypeFloat 32
+%16 = OpConstant %6 6
+%17 = OpTypeArray %15 %16
+%18 = OpTypePointer Function %17
+%19 = OpConstant %6 3
+%20 = OpTypePointer Function %15
+%21 = OpConstant %11 1
+%2 = OpFunction %4 None %5
+%22 = OpLabel
+%3 = OpVariable %18 Function
+OpBranch %23
+%23 = OpLabel
+OpBranch %28
+%28 = OpLabel
+%29 = OpULessThan %10 %8 %9
+OpBranch %30
+%30 = OpLabel
+OpBranch %31
+%31 = OpLabel
+OpBranch %36
+%36 = OpLabel
+%37 = OpSLessThan %10 %13 %14
+OpBranch %38
+%38 = OpLabel
+%39 = OpBitcast %6 %13
+%40 = OpIMul %6 %8 %19
+%41 = OpIAdd %6 %39 %40
+%42 = OpConvertUToF %15 %8
+%43 = OpAccessChain %20 %3 %41
+OpStore %43 %42
+OpBranch %34
+%34 = OpLabel
+%33 = OpIAdd %11 %13 %21
+OpBranch %44
+%44 = OpLabel
+OpBranch %46
+%46 = OpLabel
+%47 = OpSLessThan %10 %33 %14
+OpBranch %48
+%48 = OpLabel
+%49 = OpBitcast %6 %33
+%50 = OpIMul %6 %8 %19
+%51 = OpIAdd %6 %49 %50
+%52 = OpConvertUToF %15 %8
+%53 = OpAccessChain %20 %3 %51
+OpStore %53 %52
+OpBranch %54
+%54 = OpLabel
+%55 = OpIAdd %11 %33 %21
+OpBranch %56
+%56 = OpLabel
+OpBranch %58
+%58 = OpLabel
+%59 = OpSLessThan %10 %55 %14
+OpBranch %60
+%60 = OpLabel
+%61 = OpBitcast %6 %55
+%62 = OpIMul %6 %8 %19
+%63 = OpIAdd %6 %61 %62
+%64 = OpConvertUToF %15 %8
+%65 = OpAccessChain %20 %3 %63
+OpStore %65 %64
+OpBranch %66
+%66 = OpLabel
+%67 = OpIAdd %11 %55 %21
+OpBranch %35
+%35 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%25 = OpIAdd %6 %8 %21
+OpBranch %68
+%68 = OpLabel
+OpBranch %70
+%70 = OpLabel
+%71 = OpULessThan %10 %25 %9
+OpBranch %72
+%72 = OpLabel
+OpBranch %73
+%73 = OpLabel
+OpBranch %74
+%74 = OpLabel
+%75 = OpSLessThan %10 %13 %14
+OpBranch %76
+%76 = OpLabel
+%77 = OpBitcast %6 %13
+%78 = OpIMul %6 %25 %19
+%79 = OpIAdd %6 %77 %78
+%80 = OpConvertUToF %15 %25
+%81 = OpAccessChain %20 %3 %79
+OpStore %81 %80
+OpBranch %82
+%82 = OpLabel
+%83 = OpIAdd %11 %13 %21
+OpBranch %84
+%84 = OpLabel
+OpBranch %85
+%85 = OpLabel
+%86 = OpSLessThan %10 %83 %14
+OpBranch %87
+%87 = OpLabel
+%88 = OpBitcast %6 %83
+%89 = OpIMul %6 %25 %19
+%90 = OpIAdd %6 %88 %89
+%91 = OpConvertUToF %15 %25
+%92 = OpAccessChain %20 %3 %90
+OpStore %92 %91
+OpBranch %93
+%93 = OpLabel
+%94 = OpIAdd %11 %83 %21
+OpBranch %95
+%95 = OpLabel
+OpBranch %96
+%96 = OpLabel
+%97 = OpSLessThan %10 %94 %14
+OpBranch %98
+%98 = OpLabel
+%99 = OpBitcast %6 %94
+%100 = OpIMul %6 %25 %19
+%101 = OpIAdd %6 %99 %100
+%102 = OpConvertUToF %15 %25
+%103 = OpAccessChain %20 %3 %101
+OpStore %103 %102
+OpBranch %104
+%104 = OpLabel
+%105 = OpIAdd %11 %94 %21
+OpBranch %106
+%106 = OpLabel
+OpBranch %107
+%107 = OpLabel
+%108 = OpIAdd %6 %25 %21
+OpBranch %27
+%27 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, output, false);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[2];
+ for (int i = -3; i < -1; i++) {
+ out_array[3 + i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, NegativeConditionAndInit) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %23 "out_array"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 -3
+ %16 = OpConstant %6 -1
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 2
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %25 = OpConstant %6 3
+ %30 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %23 = OpVariable %22 Function
+ OpBranch %10
+ %10 = OpLabel
+ %32 = OpPhi %6 %9 %5 %31 %13
+ OpLoopMerge %12 %13 Unroll
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %32 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpIAdd %6 %32 %25
+ %28 = OpAccessChain %7 %23 %26
+ OpStore %28 %32
+ OpBranch %13
+ %13 = OpLabel
+ %31 = OpIAdd %6 %32 %30
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 -3
+%9 = OpConstant %6 -1
+%10 = OpTypeBool
+%11 = OpTypeInt 32 0
+%12 = OpConstant %11 2
+%13 = OpTypeArray %6 %12
+%14 = OpTypePointer Function %13
+%15 = OpConstant %6 3
+%16 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%17 = OpLabel
+%3 = OpVariable %14 Function
+OpBranch %18
+%18 = OpLabel
+OpBranch %23
+%23 = OpLabel
+%24 = OpSLessThan %10 %8 %9
+OpBranch %25
+%25 = OpLabel
+%26 = OpIAdd %6 %8 %15
+%27 = OpAccessChain %7 %3 %26
+OpStore %27 %8
+OpBranch %21
+%21 = OpLabel
+%20 = OpIAdd %6 %8 %16
+OpBranch %28
+%28 = OpLabel
+OpBranch %30
+%30 = OpLabel
+%31 = OpSLessThan %10 %20 %9
+OpBranch %32
+%32 = OpLabel
+%33 = OpIAdd %6 %20 %15
+%34 = OpAccessChain %7 %3 %33
+OpStore %34 %20
+OpBranch %35
+%35 = OpLabel
+%36 = OpIAdd %6 %20 %16
+OpBranch %22
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ // SinglePassRunAndCheck<LoopUnroller>(text, expected, false);
+
+ Function* f = spvtest::GetFunction(module, 4);
+
+ LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f);
+ EXPECT_EQ(loop_descriptor.NumLoops(), 1u);
+
+ Loop& loop = loop_descriptor.GetLoopByIndex(0);
+
+ EXPECT_TRUE(loop.HasUnrollLoopControl());
+
+ BasicBlock* condition = loop.FindConditionBlock();
+ EXPECT_EQ(condition->id(), 14u);
+
+ Instruction* induction = loop.FindConditionVariable(condition);
+ EXPECT_EQ(induction->result_id(), 32u);
+
+ LoopUtils loop_utils{context.get(), &loop};
+ EXPECT_TRUE(loop_utils.CanPerformUnroll());
+
+ size_t iterations = 0;
+ EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(),
+ &iterations));
+ EXPECT_EQ(iterations, 2u);
+ SinglePassRunAndCheck<LoopUnroller>(text, expected, false);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[9];
+ for (int i = -10; i < -1; i++) {
+ out_array[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, NegativeConditionAndInitResidualUnroll) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %23 "out_array"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 -10
+ %16 = OpConstant %6 -1
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 0
+ %20 = OpConstant %19 9
+ %21 = OpTypeArray %6 %20
+ %22 = OpTypePointer Function %21
+ %25 = OpConstant %6 10
+ %30 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %23 = OpVariable %22 Function
+ OpBranch %10
+ %10 = OpLabel
+ %32 = OpPhi %6 %9 %5 %31 %13
+ OpLoopMerge %12 %13 Unroll
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %32 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %26 = OpIAdd %6 %32 %25
+ %28 = OpAccessChain %7 %23 %26
+ OpStore %28 %32
+ OpBranch %13
+ %13 = OpLabel
+ %31 = OpIAdd %6 %32 %30
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 -10
+%9 = OpConstant %6 -1
+%10 = OpTypeBool
+%11 = OpTypeInt 32 0
+%12 = OpConstant %11 9
+%13 = OpTypeArray %6 %12
+%14 = OpTypePointer Function %13
+%15 = OpConstant %6 10
+%16 = OpConstant %6 1
+%48 = OpConstant %6 -9
+%2 = OpFunction %4 None %5
+%17 = OpLabel
+%3 = OpVariable %14 Function
+OpBranch %18
+%18 = OpLabel
+%19 = OpPhi %6 %8 %17 %20 %21
+OpLoopMerge %28 %21 Unroll
+OpBranch %23
+%23 = OpLabel
+%24 = OpSLessThan %10 %19 %48
+OpBranchConditional %24 %25 %28
+%25 = OpLabel
+%26 = OpIAdd %6 %19 %15
+%27 = OpAccessChain %7 %3 %26
+OpStore %27 %19
+OpBranch %21
+%21 = OpLabel
+%20 = OpIAdd %6 %19 %16
+OpBranch %18
+%28 = OpLabel
+OpBranch %29
+%29 = OpLabel
+%30 = OpPhi %6 %19 %28 %47 %46
+OpLoopMerge %38 %46 DontUnroll
+OpBranch %31
+%31 = OpLabel
+%32 = OpSLessThan %10 %30 %9
+OpBranchConditional %32 %33 %38
+%33 = OpLabel
+%34 = OpIAdd %6 %30 %15
+%35 = OpAccessChain %7 %3 %34
+OpStore %35 %30
+OpBranch %36
+%36 = OpLabel
+%37 = OpIAdd %6 %30 %16
+OpBranch %39
+%39 = OpLabel
+OpBranch %41
+%41 = OpLabel
+%42 = OpSLessThan %10 %37 %9
+OpBranch %43
+%43 = OpLabel
+%44 = OpIAdd %6 %37 %15
+%45 = OpAccessChain %7 %3 %44
+OpStore %45 %37
+OpBranch %46
+%46 = OpLabel
+%47 = OpIAdd %6 %37 %16
+OpBranch %29
+%38 = OpLabel
+OpReturn
+%22 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ Function* f = spvtest::GetFunction(module, 4);
+
+ LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f);
+ EXPECT_EQ(loop_descriptor.NumLoops(), 1u);
+
+ Loop& loop = loop_descriptor.GetLoopByIndex(0);
+
+ EXPECT_TRUE(loop.HasUnrollLoopControl());
+
+ BasicBlock* condition = loop.FindConditionBlock();
+ EXPECT_EQ(condition->id(), 14u);
+
+ Instruction* induction = loop.FindConditionVariable(condition);
+ EXPECT_EQ(induction->result_id(), 32u);
+
+ LoopUtils loop_utils{context.get(), &loop};
+ EXPECT_TRUE(loop_utils.CanPerformUnroll());
+
+ size_t iterations = 0;
+ EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(),
+ &iterations));
+ EXPECT_EQ(iterations, 9u);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(text, expected, false);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[10];
+ for (uint i = 0; i < 2; i++) {
+ for (int x = 0; x < 5; ++x) {
+ out_array[x + i*5] = i;
+ }
+ }
+}
+*/
+TEST_F(PassClassTest, UnrollNestedLoopsValidateDescriptor) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %35 "out_array"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 2
+ %17 = OpTypeBool
+ %19 = OpTypeInt 32 1
+ %20 = OpTypePointer Function %19
+ %22 = OpConstant %19 0
+ %29 = OpConstant %19 5
+ %31 = OpTypeFloat 32
+ %32 = OpConstant %6 10
+ %33 = OpTypeArray %31 %32
+ %34 = OpTypePointer Function %33
+ %39 = OpConstant %6 5
+ %44 = OpTypePointer Function %31
+ %47 = OpConstant %19 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %35 = OpVariable %34 Function
+ OpBranch %10
+ %10 = OpLabel
+ %51 = OpPhi %6 %9 %5 %50 %13
+ OpLoopMerge %12 %13 Unroll
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpULessThan %17 %51 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %54 = OpPhi %19 %22 %11 %48 %26
+ OpLoopMerge %25 %26 Unroll
+ OpBranch %27
+ %27 = OpLabel
+ %30 = OpSLessThan %17 %54 %29
+ OpBranchConditional %30 %24 %25
+ %24 = OpLabel
+ %37 = OpBitcast %6 %54
+ %40 = OpIMul %6 %51 %39
+ %41 = OpIAdd %6 %37 %40
+ %43 = OpConvertUToF %31 %51
+ %45 = OpAccessChain %44 %35 %41
+ OpStore %45 %43
+ OpBranch %26
+ %26 = OpLabel
+ %48 = OpIAdd %19 %54 %47
+ OpBranch %23
+ %25 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %50 = OpIAdd %6 %51 %47
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ { // Test fully unroll
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f);
+ EXPECT_EQ(loop_descriptor.NumLoops(), 2u);
+
+ Loop& outer_loop = loop_descriptor.GetLoopByIndex(1);
+
+ EXPECT_TRUE(outer_loop.HasUnrollLoopControl());
+
+ Loop& inner_loop = loop_descriptor.GetLoopByIndex(0);
+
+ EXPECT_TRUE(inner_loop.HasUnrollLoopControl());
+
+ EXPECT_EQ(outer_loop.GetBlocks().size(), 9u);
+
+ EXPECT_EQ(inner_loop.GetBlocks().size(), 4u);
+ EXPECT_EQ(outer_loop.NumImmediateChildren(), 1u);
+ EXPECT_EQ(inner_loop.NumImmediateChildren(), 0u);
+
+ {
+ LoopUtils loop_utils{context.get(), &inner_loop};
+ loop_utils.FullyUnroll();
+ loop_utils.Finalize();
+ }
+
+ EXPECT_EQ(loop_descriptor.NumLoops(), 1u);
+ EXPECT_EQ(outer_loop.GetBlocks().size(), 25u);
+ EXPECT_EQ(outer_loop.NumImmediateChildren(), 0u);
+ {
+ LoopUtils loop_utils{context.get(), &outer_loop};
+ loop_utils.FullyUnroll();
+ loop_utils.Finalize();
+ }
+ EXPECT_EQ(loop_descriptor.NumLoops(), 0u);
+ }
+
+ { // Test partially unroll
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+
+ Function* f = spvtest::GetFunction(module, 4);
+ LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f);
+ EXPECT_EQ(loop_descriptor.NumLoops(), 2u);
+
+ Loop& outer_loop = loop_descriptor.GetLoopByIndex(1);
+
+ EXPECT_TRUE(outer_loop.HasUnrollLoopControl());
+
+ Loop& inner_loop = loop_descriptor.GetLoopByIndex(0);
+
+ EXPECT_TRUE(inner_loop.HasUnrollLoopControl());
+
+ EXPECT_EQ(outer_loop.GetBlocks().size(), 9u);
+
+ EXPECT_EQ(inner_loop.GetBlocks().size(), 4u);
+
+ EXPECT_EQ(outer_loop.NumImmediateChildren(), 1u);
+ EXPECT_EQ(inner_loop.NumImmediateChildren(), 0u);
+
+ LoopUtils loop_utils{context.get(), &inner_loop};
+ loop_utils.PartiallyUnroll(2);
+ loop_utils.Finalize();
+
+ // The number of loops should actually grow.
+ EXPECT_EQ(loop_descriptor.NumLoops(), 3u);
+ EXPECT_EQ(outer_loop.GetBlocks().size(), 18u);
+ EXPECT_EQ(outer_loop.NumImmediateChildren(), 2u);
+ }
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[3];
+ for (int i = 3; i > 0; --i) {
+ out_array[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, FullyUnrollNegativeStepLoopTest) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %24 "out_array"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 3
+ %16 = OpConstant %6 0
+ %17 = OpTypeBool
+ %19 = OpTypeFloat 32
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 3
+ %22 = OpTypeArray %19 %21
+ %23 = OpTypePointer Function %22
+ %28 = OpTypePointer Function %19
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %24 = OpVariable %23 Function
+ OpBranch %10
+ %10 = OpLabel
+ %33 = OpPhi %6 %9 %5 %32 %13
+ OpLoopMerge %12 %13 Unroll
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSGreaterThan %17 %33 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpConvertSToF %19 %33
+ %29 = OpAccessChain %28 %24 %33
+ OpStore %29 %27
+ OpBranch %13
+ %13 = OpLabel
+ %32 = OpISub %6 %33 %31
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 3
+%9 = OpConstant %6 0
+%10 = OpTypeBool
+%11 = OpTypeFloat 32
+%12 = OpTypeInt 32 0
+%13 = OpConstant %12 3
+%14 = OpTypeArray %11 %13
+%15 = OpTypePointer Function %14
+%16 = OpTypePointer Function %11
+%17 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%18 = OpLabel
+%3 = OpVariable %15 Function
+OpBranch %19
+%19 = OpLabel
+OpBranch %24
+%24 = OpLabel
+%25 = OpSGreaterThan %10 %8 %9
+OpBranch %26
+%26 = OpLabel
+%27 = OpConvertSToF %11 %8
+%28 = OpAccessChain %16 %3 %8
+OpStore %28 %27
+OpBranch %22
+%22 = OpLabel
+%21 = OpISub %6 %8 %17
+OpBranch %29
+%29 = OpLabel
+OpBranch %31
+%31 = OpLabel
+%32 = OpSGreaterThan %10 %21 %9
+OpBranch %33
+%33 = OpLabel
+%34 = OpConvertSToF %11 %21
+%35 = OpAccessChain %16 %3 %21
+OpStore %35 %34
+OpBranch %36
+%36 = OpLabel
+%37 = OpISub %6 %21 %17
+OpBranch %38
+%38 = OpLabel
+OpBranch %40
+%40 = OpLabel
+%41 = OpSGreaterThan %10 %37 %9
+OpBranch %42
+%42 = OpLabel
+%43 = OpConvertSToF %11 %37
+%44 = OpAccessChain %16 %3 %37
+OpStore %44 %43
+OpBranch %45
+%45 = OpLabel
+%46 = OpISub %6 %37 %17
+OpBranch %23
+%23 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, output, false);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[3];
+ for (int i = 9; i > 0; i-=3) {
+ out_array[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, FullyUnrollNegativeNonOneStepLoop) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %24 "out_array"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 9
+ %16 = OpConstant %6 0
+ %17 = OpTypeBool
+ %19 = OpTypeFloat 32
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 3
+ %22 = OpTypeArray %19 %21
+ %23 = OpTypePointer Function %22
+ %28 = OpTypePointer Function %19
+ %30 = OpConstant %6 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %24 = OpVariable %23 Function
+ OpBranch %10
+ %10 = OpLabel
+ %33 = OpPhi %6 %9 %5 %32 %13
+ OpLoopMerge %12 %13 Unroll
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSGreaterThan %17 %33 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %27 = OpConvertSToF %19 %33
+ %29 = OpAccessChain %28 %24 %33
+ OpStore %29 %27
+ OpBranch %13
+ %13 = OpLabel
+ %32 = OpISub %6 %33 %30
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 9
+%9 = OpConstant %6 0
+%10 = OpTypeBool
+%11 = OpTypeFloat 32
+%12 = OpTypeInt 32 0
+%13 = OpConstant %12 3
+%14 = OpTypeArray %11 %13
+%15 = OpTypePointer Function %14
+%16 = OpTypePointer Function %11
+%17 = OpConstant %6 3
+%2 = OpFunction %4 None %5
+%18 = OpLabel
+%3 = OpVariable %15 Function
+OpBranch %19
+%19 = OpLabel
+OpBranch %24
+%24 = OpLabel
+%25 = OpSGreaterThan %10 %8 %9
+OpBranch %26
+%26 = OpLabel
+%27 = OpConvertSToF %11 %8
+%28 = OpAccessChain %16 %3 %8
+OpStore %28 %27
+OpBranch %22
+%22 = OpLabel
+%21 = OpISub %6 %8 %17
+OpBranch %29
+%29 = OpLabel
+OpBranch %31
+%31 = OpLabel
+%32 = OpSGreaterThan %10 %21 %9
+OpBranch %33
+%33 = OpLabel
+%34 = OpConvertSToF %11 %21
+%35 = OpAccessChain %16 %3 %21
+OpStore %35 %34
+OpBranch %36
+%36 = OpLabel
+%37 = OpISub %6 %21 %17
+OpBranch %38
+%38 = OpLabel
+OpBranch %40
+%40 = OpLabel
+%41 = OpSGreaterThan %10 %37 %9
+OpBranch %42
+%42 = OpLabel
+%43 = OpConvertSToF %11 %37
+%44 = OpAccessChain %16 %3 %37
+OpStore %44 %43
+OpBranch %45
+%45 = OpLabel
+%46 = OpISub %6 %37 %17
+OpBranch %23
+%23 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, output, false);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[3];
+ for (int i = 0; i < 7; i+=3) {
+ out_array[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, FullyUnrollNonDivisibleStepLoop) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main"
+OpExecutionMode %4 OriginUpperLeft
+OpSource GLSL 410
+OpName %4 "main"
+OpName %24 "out_array"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%9 = OpConstant %6 0
+%16 = OpConstant %6 7
+%17 = OpTypeBool
+%19 = OpTypeFloat 32
+%20 = OpTypeInt 32 0
+%21 = OpConstant %20 3
+%22 = OpTypeArray %19 %21
+%23 = OpTypePointer Function %22
+%28 = OpTypePointer Function %19
+%30 = OpConstant %6 3
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%24 = OpVariable %23 Function
+OpBranch %10
+%10 = OpLabel
+%33 = OpPhi %6 %9 %5 %32 %13
+OpLoopMerge %12 %13 Unroll
+OpBranch %14
+%14 = OpLabel
+%18 = OpSLessThan %17 %33 %16
+OpBranchConditional %18 %11 %12
+%11 = OpLabel
+%27 = OpConvertSToF %19 %33
+%29 = OpAccessChain %28 %24 %33
+OpStore %29 %27
+OpBranch %13
+%13 = OpLabel
+%32 = OpIAdd %6 %33 %30
+OpBranch %10
+%12 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 0
+%9 = OpConstant %6 7
+%10 = OpTypeBool
+%11 = OpTypeFloat 32
+%12 = OpTypeInt 32 0
+%13 = OpConstant %12 3
+%14 = OpTypeArray %11 %13
+%15 = OpTypePointer Function %14
+%16 = OpTypePointer Function %11
+%17 = OpConstant %6 3
+%2 = OpFunction %4 None %5
+%18 = OpLabel
+%3 = OpVariable %15 Function
+OpBranch %19
+%19 = OpLabel
+OpBranch %24
+%24 = OpLabel
+%25 = OpSLessThan %10 %8 %9
+OpBranch %26
+%26 = OpLabel
+%27 = OpConvertSToF %11 %8
+%28 = OpAccessChain %16 %3 %8
+OpStore %28 %27
+OpBranch %22
+%22 = OpLabel
+%21 = OpIAdd %6 %8 %17
+OpBranch %29
+%29 = OpLabel
+OpBranch %31
+%31 = OpLabel
+%32 = OpSLessThan %10 %21 %9
+OpBranch %33
+%33 = OpLabel
+%34 = OpConvertSToF %11 %21
+%35 = OpAccessChain %16 %3 %21
+OpStore %35 %34
+OpBranch %36
+%36 = OpLabel
+%37 = OpIAdd %6 %21 %17
+OpBranch %38
+%38 = OpLabel
+OpBranch %40
+%40 = OpLabel
+%41 = OpSLessThan %10 %37 %9
+OpBranch %42
+%42 = OpLabel
+%43 = OpConvertSToF %11 %37
+%44 = OpAccessChain %16 %3 %37
+OpStore %44 %43
+OpBranch %45
+%45 = OpLabel
+%46 = OpIAdd %6 %37 %17
+OpBranch %23
+%23 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, output, false);
+}
+
+/*
+Generated from the following GLSL
+#version 410 core
+void main() {
+ float out_array[4];
+ for (int i = 11; i > 0; i-=3) {
+ out_array[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, FullyUnrollNegativeNonDivisibleStepLoop) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %4 "main"
+OpExecutionMode %4 OriginUpperLeft
+OpSource GLSL 410
+OpName %4 "main"
+OpName %24 "out_array"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%9 = OpConstant %6 11
+%16 = OpConstant %6 0
+%17 = OpTypeBool
+%19 = OpTypeFloat 32
+%20 = OpTypeInt 32 0
+%21 = OpConstant %20 4
+%22 = OpTypeArray %19 %21
+%23 = OpTypePointer Function %22
+%28 = OpTypePointer Function %19
+%30 = OpConstant %6 3
+%4 = OpFunction %2 None %3
+%5 = OpLabel
+%24 = OpVariable %23 Function
+OpBranch %10
+%10 = OpLabel
+%33 = OpPhi %6 %9 %5 %32 %13
+OpLoopMerge %12 %13 Unroll
+OpBranch %14
+%14 = OpLabel
+%18 = OpSGreaterThan %17 %33 %16
+OpBranchConditional %18 %11 %12
+%11 = OpLabel
+%27 = OpConvertSToF %19 %33
+%29 = OpAccessChain %28 %24 %33
+OpStore %29 %27
+OpBranch %13
+%13 = OpLabel
+%32 = OpISub %6 %33 %30
+OpBranch %10
+%12 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "out_array"
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypePointer Function %6
+%8 = OpConstant %6 11
+%9 = OpConstant %6 0
+%10 = OpTypeBool
+%11 = OpTypeFloat 32
+%12 = OpTypeInt 32 0
+%13 = OpConstant %12 4
+%14 = OpTypeArray %11 %13
+%15 = OpTypePointer Function %14
+%16 = OpTypePointer Function %11
+%17 = OpConstant %6 3
+%2 = OpFunction %4 None %5
+%18 = OpLabel
+%3 = OpVariable %15 Function
+OpBranch %19
+%19 = OpLabel
+OpBranch %24
+%24 = OpLabel
+%25 = OpSGreaterThan %10 %8 %9
+OpBranch %26
+%26 = OpLabel
+%27 = OpConvertSToF %11 %8
+%28 = OpAccessChain %16 %3 %8
+OpStore %28 %27
+OpBranch %22
+%22 = OpLabel
+%21 = OpISub %6 %8 %17
+OpBranch %29
+%29 = OpLabel
+OpBranch %31
+%31 = OpLabel
+%32 = OpSGreaterThan %10 %21 %9
+OpBranch %33
+%33 = OpLabel
+%34 = OpConvertSToF %11 %21
+%35 = OpAccessChain %16 %3 %21
+OpStore %35 %34
+OpBranch %36
+%36 = OpLabel
+%37 = OpISub %6 %21 %17
+OpBranch %38
+%38 = OpLabel
+OpBranch %40
+%40 = OpLabel
+%41 = OpSGreaterThan %10 %37 %9
+OpBranch %42
+%42 = OpLabel
+%43 = OpConvertSToF %11 %37
+%44 = OpAccessChain %16 %3 %37
+OpStore %44 %43
+OpBranch %45
+%45 = OpLabel
+%46 = OpISub %6 %37 %17
+OpBranch %47
+%47 = OpLabel
+OpBranch %49
+%49 = OpLabel
+%50 = OpSGreaterThan %10 %46 %9
+OpBranch %51
+%51 = OpLabel
+%52 = OpConvertSToF %11 %46
+%53 = OpAccessChain %16 %3 %46
+OpStore %53 %52
+OpBranch %54
+%54 = OpLabel
+%55 = OpISub %6 %46 %17
+OpBranch %23
+%23 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, output, false);
+}
+
+// With LocalMultiStoreElimPass
+static const std::string multiple_phi_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %8 "foo("
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypeFunction %6
+ %10 = OpTypePointer Function %6
+ %12 = OpConstant %6 0
+ %14 = OpConstant %6 3
+ %22 = OpConstant %6 6
+ %23 = OpTypeBool
+ %31 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %40 = OpFunctionCall %6 %8
+ OpReturn
+ OpFunctionEnd
+ %8 = OpFunction %6 None %7
+ %9 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %41 = OpPhi %6 %12 %9 %34 %19
+ %42 = OpPhi %6 %14 %9 %29 %19
+ %43 = OpPhi %6 %12 %9 %32 %19
+ OpLoopMerge %18 %19 Unroll
+ OpBranch %20
+ %20 = OpLabel
+ %24 = OpSLessThan %23 %43 %22
+ OpBranchConditional %24 %17 %18
+ %17 = OpLabel
+ %27 = OpIMul %6 %43 %41
+ %29 = OpIAdd %6 %42 %27
+ OpBranch %19
+ %19 = OpLabel
+ %32 = OpIAdd %6 %43 %31
+ %34 = OpISub %6 %41 %31
+ OpBranch %16
+ %18 = OpLabel
+ %37 = OpIAdd %6 %42 %41
+ OpReturnValue %37
+ OpFunctionEnd
+ )";
+
+TEST_F(PassClassTest, PartiallyUnrollResidualMultipleInductionVariables) {
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "foo("
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypeFunction %6
+%8 = OpTypePointer Function %6
+%9 = OpConstant %6 0
+%10 = OpConstant %6 3
+%11 = OpConstant %6 6
+%12 = OpTypeBool
+%13 = OpConstant %6 1
+%82 = OpTypeInt 32 0
+%83 = OpConstant %82 2
+%2 = OpFunction %4 None %5
+%14 = OpLabel
+%15 = OpFunctionCall %6 %3
+OpReturn
+OpFunctionEnd
+%3 = OpFunction %6 None %7
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpPhi %6 %9 %16 %19 %20
+%21 = OpPhi %6 %10 %16 %22 %20
+%23 = OpPhi %6 %9 %16 %24 %20
+OpLoopMerge %31 %20 Unroll
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %23 %83
+OpBranchConditional %27 %28 %31
+%28 = OpLabel
+%29 = OpIMul %6 %23 %18
+%22 = OpIAdd %6 %21 %29
+OpBranch %20
+%20 = OpLabel
+%24 = OpIAdd %6 %23 %13
+%19 = OpISub %6 %18 %13
+OpBranch %17
+%31 = OpLabel
+OpBranch %32
+%32 = OpLabel
+%33 = OpPhi %6 %18 %31 %81 %79
+%34 = OpPhi %6 %21 %31 %78 %79
+%35 = OpPhi %6 %23 %31 %80 %79
+OpLoopMerge %44 %79 DontUnroll
+OpBranch %36
+%36 = OpLabel
+%37 = OpSLessThan %12 %35 %11
+OpBranchConditional %37 %38 %44
+%38 = OpLabel
+%39 = OpIMul %6 %35 %33
+%40 = OpIAdd %6 %34 %39
+OpBranch %41
+%41 = OpLabel
+%42 = OpIAdd %6 %35 %13
+%43 = OpISub %6 %33 %13
+OpBranch %46
+%46 = OpLabel
+OpBranch %50
+%50 = OpLabel
+%51 = OpSLessThan %12 %42 %11
+OpBranch %52
+%52 = OpLabel
+%53 = OpIMul %6 %42 %43
+%54 = OpIAdd %6 %40 %53
+OpBranch %55
+%55 = OpLabel
+%56 = OpIAdd %6 %42 %13
+%57 = OpISub %6 %43 %13
+OpBranch %58
+%58 = OpLabel
+OpBranch %62
+%62 = OpLabel
+%63 = OpSLessThan %12 %56 %11
+OpBranch %64
+%64 = OpLabel
+%65 = OpIMul %6 %56 %57
+%66 = OpIAdd %6 %54 %65
+OpBranch %67
+%67 = OpLabel
+%68 = OpIAdd %6 %56 %13
+%69 = OpISub %6 %57 %13
+OpBranch %70
+%70 = OpLabel
+OpBranch %74
+%74 = OpLabel
+%75 = OpSLessThan %12 %68 %11
+OpBranch %76
+%76 = OpLabel
+%77 = OpIMul %6 %68 %69
+%78 = OpIAdd %6 %66 %77
+OpBranch %79
+%79 = OpLabel
+%80 = OpIAdd %6 %68 %13
+%81 = OpISub %6 %69 %13
+OpBranch %32
+%44 = OpLabel
+%45 = OpIAdd %6 %34 %33
+OpReturnValue %45
+%25 = OpLabel
+%30 = OpIAdd %6 %34 %33
+OpReturnValue %30
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << multiple_phi_shader << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<4>>(multiple_phi_shader, output,
+ false);
+}
+
+TEST_F(PassClassTest, PartiallyUnrollMultipleInductionVariables) {
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "foo("
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypeFunction %6
+%8 = OpTypePointer Function %6
+%9 = OpConstant %6 0
+%10 = OpConstant %6 3
+%11 = OpConstant %6 6
+%12 = OpTypeBool
+%13 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%14 = OpLabel
+%15 = OpFunctionCall %6 %3
+OpReturn
+OpFunctionEnd
+%3 = OpFunction %6 None %7
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+%18 = OpPhi %6 %9 %16 %42 %40
+%21 = OpPhi %6 %10 %16 %39 %40
+%23 = OpPhi %6 %9 %16 %41 %40
+OpLoopMerge %25 %40 DontUnroll
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %23 %11
+OpBranchConditional %27 %28 %25
+%28 = OpLabel
+%29 = OpIMul %6 %23 %18
+%22 = OpIAdd %6 %21 %29
+OpBranch %20
+%20 = OpLabel
+%24 = OpIAdd %6 %23 %13
+%19 = OpISub %6 %18 %13
+OpBranch %31
+%31 = OpLabel
+OpBranch %35
+%35 = OpLabel
+%36 = OpSLessThan %12 %24 %11
+OpBranch %37
+%37 = OpLabel
+%38 = OpIMul %6 %24 %19
+%39 = OpIAdd %6 %22 %38
+OpBranch %40
+%40 = OpLabel
+%41 = OpIAdd %6 %24 %13
+%42 = OpISub %6 %19 %13
+OpBranch %17
+%25 = OpLabel
+%30 = OpIAdd %6 %21 %18
+OpReturnValue %30
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << multiple_phi_shader << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(multiple_phi_shader, output,
+ false);
+}
+
+TEST_F(PassClassTest, FullyUnrollMultipleInductionVariables) {
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 410
+OpName %2 "main"
+OpName %3 "foo("
+%4 = OpTypeVoid
+%5 = OpTypeFunction %4
+%6 = OpTypeInt 32 1
+%7 = OpTypeFunction %6
+%8 = OpTypePointer Function %6
+%9 = OpConstant %6 0
+%10 = OpConstant %6 3
+%11 = OpConstant %6 6
+%12 = OpTypeBool
+%13 = OpConstant %6 1
+%2 = OpFunction %4 None %5
+%14 = OpLabel
+%15 = OpFunctionCall %6 %3
+OpReturn
+OpFunctionEnd
+%3 = OpFunction %6 None %7
+%16 = OpLabel
+OpBranch %17
+%17 = OpLabel
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %12 %9 %11
+OpBranch %28
+%28 = OpLabel
+%29 = OpIMul %6 %9 %9
+%22 = OpIAdd %6 %10 %29
+OpBranch %20
+%20 = OpLabel
+%24 = OpIAdd %6 %9 %13
+%19 = OpISub %6 %9 %13
+OpBranch %31
+%31 = OpLabel
+OpBranch %35
+%35 = OpLabel
+%36 = OpSLessThan %12 %24 %11
+OpBranch %37
+%37 = OpLabel
+%38 = OpIMul %6 %24 %19
+%39 = OpIAdd %6 %22 %38
+OpBranch %40
+%40 = OpLabel
+%41 = OpIAdd %6 %24 %13
+%42 = OpISub %6 %19 %13
+OpBranch %43
+%43 = OpLabel
+OpBranch %47
+%47 = OpLabel
+%48 = OpSLessThan %12 %41 %11
+OpBranch %49
+%49 = OpLabel
+%50 = OpIMul %6 %41 %42
+%51 = OpIAdd %6 %39 %50
+OpBranch %52
+%52 = OpLabel
+%53 = OpIAdd %6 %41 %13
+%54 = OpISub %6 %42 %13
+OpBranch %55
+%55 = OpLabel
+OpBranch %59
+%59 = OpLabel
+%60 = OpSLessThan %12 %53 %11
+OpBranch %61
+%61 = OpLabel
+%62 = OpIMul %6 %53 %54
+%63 = OpIAdd %6 %51 %62
+OpBranch %64
+%64 = OpLabel
+%65 = OpIAdd %6 %53 %13
+%66 = OpISub %6 %54 %13
+OpBranch %67
+%67 = OpLabel
+OpBranch %71
+%71 = OpLabel
+%72 = OpSLessThan %12 %65 %11
+OpBranch %73
+%73 = OpLabel
+%74 = OpIMul %6 %65 %66
+%75 = OpIAdd %6 %63 %74
+OpBranch %76
+%76 = OpLabel
+%77 = OpIAdd %6 %65 %13
+%78 = OpISub %6 %66 %13
+OpBranch %79
+%79 = OpLabel
+OpBranch %83
+%83 = OpLabel
+%84 = OpSLessThan %12 %77 %11
+OpBranch %85
+%85 = OpLabel
+%86 = OpIMul %6 %77 %78
+%87 = OpIAdd %6 %75 %86
+OpBranch %88
+%88 = OpLabel
+%89 = OpIAdd %6 %77 %13
+%90 = OpISub %6 %78 %13
+OpBranch %25
+%25 = OpLabel
+%30 = OpIAdd %6 %87 %90
+OpReturnValue %30
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << multiple_phi_shader << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(multiple_phi_shader, output, false);
+}
+
+/*
+Generated from the following GLSL
+#version 440 core
+void main()
+{
+ int j = 0;
+ for (int i = 0; i <= 2; ++i)
+ ++j;
+
+ for (int i = 1; i >= 0; --i)
+ ++j;
+}
+*/
+TEST_F(PassClassTest, FullyUnrollEqualToOperations) {
+ // With LocalMultiStoreElimPass
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 440
+ OpName %4 "main"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %17 = OpConstant %6 2
+ %18 = OpTypeBool
+ %21 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ %37 = OpPhi %6 %9 %5 %22 %14
+ %38 = OpPhi %6 %9 %5 %24 %14
+ OpLoopMerge %13 %14 Unroll
+ OpBranch %15
+ %15 = OpLabel
+ %19 = OpSLessThanEqual %18 %38 %17
+ OpBranchConditional %19 %12 %13
+ %12 = OpLabel
+ %22 = OpIAdd %6 %37 %21
+ OpBranch %14
+ %14 = OpLabel
+ %24 = OpIAdd %6 %38 %21
+ OpBranch %11
+ %13 = OpLabel
+ OpBranch %26
+ %26 = OpLabel
+ %39 = OpPhi %6 %37 %13 %34 %29
+ %40 = OpPhi %6 %21 %13 %36 %29
+ OpLoopMerge %28 %29 Unroll
+ OpBranch %30
+ %30 = OpLabel
+ %32 = OpSGreaterThanEqual %18 %40 %9
+ OpBranchConditional %32 %27 %28
+ %27 = OpLabel
+ %34 = OpIAdd %6 %39 %21
+ OpBranch %29
+ %29 = OpLabel
+ %36 = OpISub %6 %40 %21
+ OpBranch %26
+ %28 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string output = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 440
+OpName %2 "main"
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpTypePointer Function %5
+%7 = OpConstant %5 0
+%8 = OpConstant %5 2
+%9 = OpTypeBool
+%10 = OpConstant %5 1
+%2 = OpFunction %3 None %4
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+OpBranch %19
+%19 = OpLabel
+%20 = OpSLessThanEqual %9 %7 %8
+OpBranch %21
+%21 = OpLabel
+%14 = OpIAdd %5 %7 %10
+OpBranch %15
+%15 = OpLabel
+%17 = OpIAdd %5 %7 %10
+OpBranch %41
+%41 = OpLabel
+OpBranch %44
+%44 = OpLabel
+%45 = OpSLessThanEqual %9 %17 %8
+OpBranch %46
+%46 = OpLabel
+%47 = OpIAdd %5 %14 %10
+OpBranch %48
+%48 = OpLabel
+%49 = OpIAdd %5 %17 %10
+OpBranch %50
+%50 = OpLabel
+OpBranch %53
+%53 = OpLabel
+%54 = OpSLessThanEqual %9 %49 %8
+OpBranch %55
+%55 = OpLabel
+%56 = OpIAdd %5 %47 %10
+OpBranch %57
+%57 = OpLabel
+%58 = OpIAdd %5 %49 %10
+OpBranch %18
+%18 = OpLabel
+OpBranch %22
+%22 = OpLabel
+OpBranch %29
+%29 = OpLabel
+%30 = OpSGreaterThanEqual %9 %10 %7
+OpBranch %31
+%31 = OpLabel
+%24 = OpIAdd %5 %56 %10
+OpBranch %25
+%25 = OpLabel
+%27 = OpISub %5 %10 %10
+OpBranch %32
+%32 = OpLabel
+OpBranch %35
+%35 = OpLabel
+%36 = OpSGreaterThanEqual %9 %27 %7
+OpBranch %37
+%37 = OpLabel
+%38 = OpIAdd %5 %24 %10
+OpBranch %39
+%39 = OpLabel
+%40 = OpISub %5 %27 %10
+OpBranch %28
+%28 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << text << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(text, output, false);
+}
+
+// With LocalMultiStoreElimPass
+const std::string condition_in_header = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %o
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 430
+ OpDecorate %o Location 0
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %int_n2 = OpConstant %int -2
+ %int_2 = OpConstant %int 2
+ %bool = OpTypeBool
+ %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+ %o = OpVariable %_ptr_Output_float Output
+ %float_1 = OpConstant %float 1
+ %main = OpFunction %void None %6
+ %15 = OpLabel
+ OpBranch %16
+ %16 = OpLabel
+ %27 = OpPhi %int %int_n2 %15 %26 %18
+ %21 = OpSLessThanEqual %bool %27 %int_2
+ OpLoopMerge %17 %18 Unroll
+ OpBranchConditional %21 %22 %17
+ %22 = OpLabel
+ %23 = OpLoad %float %o
+ %24 = OpFAdd %float %23 %float_1
+ OpStore %o %24
+ OpBranch %18
+ %18 = OpLabel
+ %26 = OpIAdd %int %27 %int_2
+ OpBranch %16
+ %17 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+TEST_F(PassClassTest, FullyUnrollConditionIsInHeaderBlock) {
+ const std::string output = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpSource GLSL 430
+OpDecorate %2 Location 0
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpConstant %5 -2
+%7 = OpConstant %5 2
+%8 = OpTypeBool
+%9 = OpTypeFloat 32
+%10 = OpTypePointer Output %9
+%2 = OpVariable %10 Output
+%11 = OpConstant %9 1
+%1 = OpFunction %3 None %4
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+%17 = OpSLessThanEqual %8 %6 %7
+OpBranch %19
+%19 = OpLabel
+%20 = OpLoad %9 %2
+%21 = OpFAdd %9 %20 %11
+OpStore %2 %21
+OpBranch %16
+%16 = OpLabel
+%15 = OpIAdd %5 %6 %7
+OpBranch %22
+%22 = OpLabel
+%24 = OpSLessThanEqual %8 %15 %7
+OpBranch %25
+%25 = OpLabel
+%26 = OpLoad %9 %2
+%27 = OpFAdd %9 %26 %11
+OpStore %2 %27
+OpBranch %28
+%28 = OpLabel
+%29 = OpIAdd %5 %15 %7
+OpBranch %30
+%30 = OpLabel
+%32 = OpSLessThanEqual %8 %29 %7
+OpBranch %33
+%33 = OpLabel
+%34 = OpLoad %9 %2
+%35 = OpFAdd %9 %34 %11
+OpStore %2 %35
+OpBranch %36
+%36 = OpLabel
+%37 = OpIAdd %5 %29 %7
+OpBranch %18
+%18 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, condition_in_header,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << condition_in_header << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<LoopUnroller>(condition_in_header, output, false);
+}
+
+TEST_F(PassClassTest, PartiallyUnrollResidualConditionIsInHeaderBlock) {
+ const std::string output = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "main" %2
+OpExecutionMode %1 OriginUpperLeft
+OpSource GLSL 430
+OpDecorate %2 Location 0
+%3 = OpTypeVoid
+%4 = OpTypeFunction %3
+%5 = OpTypeInt 32 1
+%6 = OpConstant %5 -2
+%7 = OpConstant %5 2
+%8 = OpTypeBool
+%9 = OpTypeFloat 32
+%10 = OpTypePointer Output %9
+%2 = OpVariable %10 Output
+%11 = OpConstant %9 1
+%40 = OpTypeInt 32 0
+%41 = OpConstant %40 1
+%1 = OpFunction %3 None %4
+%12 = OpLabel
+OpBranch %13
+%13 = OpLabel
+%14 = OpPhi %5 %6 %12 %15 %16
+%17 = OpSLessThanEqual %8 %14 %41
+OpLoopMerge %22 %16 Unroll
+OpBranchConditional %17 %19 %22
+%19 = OpLabel
+%20 = OpLoad %9 %2
+%21 = OpFAdd %9 %20 %11
+OpStore %2 %21
+OpBranch %16
+%16 = OpLabel
+%15 = OpIAdd %5 %14 %7
+OpBranch %13
+%22 = OpLabel
+OpBranch %23
+%23 = OpLabel
+%24 = OpPhi %5 %14 %22 %39 %38
+%25 = OpSLessThanEqual %8 %24 %7
+OpLoopMerge %31 %38 DontUnroll
+OpBranchConditional %25 %26 %31
+%26 = OpLabel
+%27 = OpLoad %9 %2
+%28 = OpFAdd %9 %27 %11
+OpStore %2 %28
+OpBranch %29
+%29 = OpLabel
+%30 = OpIAdd %5 %24 %7
+OpBranch %32
+%32 = OpLabel
+%34 = OpSLessThanEqual %8 %30 %7
+OpBranch %35
+%35 = OpLabel
+%36 = OpLoad %9 %2
+%37 = OpFAdd %9 %36 %11
+OpStore %2 %37
+OpBranch %38
+%38 = OpLabel
+%39 = OpIAdd %5 %30 %7
+OpBranch %23
+%31 = OpLabel
+OpReturn
+%18 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, condition_in_header,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n"
+ << condition_in_header << std::endl;
+
+ LoopUnroller loop_unroller;
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<2>>(condition_in_header, output,
+ false);
+}
+
+/*
+Generated from following GLSL with latch block artificially inserted to be
+seperate from continue.
+#version 430
+void main(void) {
+ float x[10];
+ for (int i = 0; i < 10; ++i) {
+ x[i] = i;
+ }
+}
+*/
+TEST_F(PassClassTest, PartiallyUnrollLatchNotContinue) {
+ const std::string text = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "x"
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeInt 32 1
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %10 = OpConstant %7 10
+ %11 = OpTypeBool
+ %12 = OpTypeFloat 32
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 10
+ %15 = OpTypeArray %12 %14
+ %16 = OpTypePointer Function %15
+ %17 = OpTypePointer Function %12
+ %18 = OpConstant %7 1
+ %2 = OpFunction %5 None %6
+ %19 = OpLabel
+ %3 = OpVariable %8 Function
+ %4 = OpVariable %16 Function
+ OpStore %3 %9
+ OpBranch %20
+ %20 = OpLabel
+ %21 = OpPhi %7 %9 %19 %22 %30
+ OpLoopMerge %24 %23 Unroll
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpSLessThan %11 %21 %10
+ OpBranchConditional %26 %27 %24
+ %27 = OpLabel
+ %28 = OpConvertSToF %12 %21
+ %29 = OpAccessChain %17 %4 %21
+ OpStore %29 %28
+ OpBranch %23
+ %23 = OpLabel
+ %22 = OpIAdd %7 %21 %18
+ OpStore %3 %22
+ OpBranch %30
+ %30 = OpLabel
+ OpBranch %20
+ %24 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const std::string expected = R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %2 "main"
+OpExecutionMode %2 OriginUpperLeft
+OpSource GLSL 430
+OpName %2 "main"
+OpName %3 "i"
+OpName %4 "x"
+%5 = OpTypeVoid
+%6 = OpTypeFunction %5
+%7 = OpTypeInt 32 1
+%8 = OpTypePointer Function %7
+%9 = OpConstant %7 0
+%10 = OpConstant %7 10
+%11 = OpTypeBool
+%12 = OpTypeFloat 32
+%13 = OpTypeInt 32 0
+%14 = OpConstant %13 10
+%15 = OpTypeArray %12 %14
+%16 = OpTypePointer Function %15
+%17 = OpTypePointer Function %12
+%18 = OpConstant %7 1
+%63 = OpConstant %13 1
+%2 = OpFunction %5 None %6
+%19 = OpLabel
+%3 = OpVariable %8 Function
+%4 = OpVariable %16 Function
+OpStore %3 %9
+OpBranch %20
+%20 = OpLabel
+%21 = OpPhi %7 %9 %19 %22 %23
+OpLoopMerge %31 %25 Unroll
+OpBranch %26
+%26 = OpLabel
+%27 = OpSLessThan %11 %21 %63
+OpBranchConditional %27 %28 %31
+%28 = OpLabel
+%29 = OpConvertSToF %12 %21
+%30 = OpAccessChain %17 %4 %21
+OpStore %30 %29
+OpBranch %25
+%25 = OpLabel
+%22 = OpIAdd %7 %21 %18
+OpStore %3 %22
+OpBranch %23
+%23 = OpLabel
+OpBranch %20
+%31 = OpLabel
+OpBranch %32
+%32 = OpLabel
+%33 = OpPhi %7 %21 %31 %61 %62
+OpLoopMerge %42 %60 DontUnroll
+OpBranch %34
+%34 = OpLabel
+%35 = OpSLessThan %11 %33 %10
+OpBranchConditional %35 %36 %42
+%36 = OpLabel
+%37 = OpConvertSToF %12 %33
+%38 = OpAccessChain %17 %4 %33
+OpStore %38 %37
+OpBranch %39
+%39 = OpLabel
+%40 = OpIAdd %7 %33 %18
+OpStore %3 %40
+OpBranch %41
+%41 = OpLabel
+OpBranch %43
+%43 = OpLabel
+OpBranch %45
+%45 = OpLabel
+%46 = OpSLessThan %11 %40 %10
+OpBranch %47
+%47 = OpLabel
+%48 = OpConvertSToF %12 %40
+%49 = OpAccessChain %17 %4 %40
+OpStore %49 %48
+OpBranch %50
+%50 = OpLabel
+%51 = OpIAdd %7 %40 %18
+OpStore %3 %51
+OpBranch %52
+%52 = OpLabel
+OpBranch %53
+%53 = OpLabel
+OpBranch %55
+%55 = OpLabel
+%56 = OpSLessThan %11 %51 %10
+OpBranch %57
+%57 = OpLabel
+%58 = OpConvertSToF %12 %51
+%59 = OpAccessChain %17 %4 %51
+OpStore %59 %58
+OpBranch %60
+%60 = OpLabel
+%61 = OpIAdd %7 %51 %18
+OpStore %3 %61
+OpBranch %62
+%62 = OpLabel
+OpBranch %32
+%42 = OpLabel
+OpReturn
+%24 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
+ SinglePassRunAndCheck<PartialUnrollerTestPass<3>>(text, expected, true);
+
+ // Make sure the latch block information is preserved and propagated correctly
+ // by the pass.
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ PartialUnrollerTestPass<3> unroller;
+ unroller.SetContextForTesting(context.get());
+ unroller.Process();
+
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ LoopDescriptor ld{context.get(), f};
+
+ EXPECT_EQ(ld.NumLoops(), 2u);
+
+ Loop& loop_1 = ld.GetLoopByIndex(0u);
+ EXPECT_NE(loop_1.GetLatchBlock(), loop_1.GetContinueBlock());
+
+ Loop& loop_2 = ld.GetLoopByIndex(1u);
+ EXPECT_NE(loop_2.GetLatchBlock(), loop_2.GetContinueBlock());
+}
+
+// Test that a loop with a self-referencing OpPhi instruction is handled
+// correctly.
+TEST_F(PassClassTest, OpPhiSelfReference) {
+ const std::string text = R"(
+ ; Find the two adds from the unrolled loop
+ ; CHECK: OpIAdd
+ ; CHECK: OpIAdd
+ ; CHECK: OpIAdd %uint %uint_0 %uint_1
+ ; CHECK-NEXT: OpReturn
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %2 "main"
+ OpExecutionMode %2 LocalSize 8 8 1
+ OpSource HLSL 600
+ %uint = OpTypeInt 32 0
+ %void = OpTypeVoid
+ %5 = OpTypeFunction %void
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %2 = OpFunction %void None %5
+ %10 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %20 = OpPhi %uint %uint_0 %10 %20 %21
+ %22 = OpPhi %uint %uint_0 %10 %23 %21
+ %24 = OpULessThanEqual %bool %22 %uint_1
+ OpLoopMerge %25 %21 Unroll
+ OpBranchConditional %24 %21 %25
+ %21 = OpLabel
+ %23 = OpIAdd %uint %22 %uint_1
+ OpBranch %19
+ %25 = OpLabel
+ %14 = OpIAdd %uint %20 %uint_1
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const bool kFullyUnroll = true;
+ const uint32_t kUnrollFactor = 0;
+ SinglePassRunAndMatch<opt::LoopUnroller>(text, true, kFullyUnroll,
+ kUnrollFactor);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/loop_optimizations/unswitch.cpp b/test/opt/loop_optimizations/unswitch.cpp
new file mode 100644
index 0000000..dc7073f
--- /dev/null
+++ b/test/opt/loop_optimizations/unswitch.cpp
@@ -0,0 +1,967 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using UnswitchTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 450 core
+uniform vec4 c;
+void main() {
+ int i = 0;
+ int j = 0;
+ bool cond = c[0] == 0;
+ for (; i < 10; i++, j++) {
+ if (cond) {
+ i++;
+ }
+ else {
+ j++;
+ }
+ }
+}
+*/
+TEST_F(UnswitchTest, SimpleUnswitch) {
+ const std::string text = R"(
+; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual
+; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]]
+
+; Loop specialized for false.
+; CHECK: [[loop_f]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_j:%\w+]] [[continue]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; [[loop_body]] = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge:%\w+]] None
+; CHECK: OpBranchConditional %false [[bb1:%\w+]] [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: [[inc_j:%\w+]] = OpIAdd %int [[phi_j]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: [[inc_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[sel_merge]] = OpLabel
+; CHECK: OpBranch [[if_merge]]
+
+; Loop specialized for true.
+; CHECK: [[loop_t]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: [[phi_j:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_j:%\w+]] [[continue]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; [[loop_body]] = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge:%\w+]] None
+; CHECK: OpBranchConditional %true [[bb1:%\w+]] [[bb2:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: [[inc_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: [[inc_j:%\w+]] = OpIAdd %int [[phi_j]] %int_1
+; CHECK-NEXT: OpBranch [[sel_merge]]
+; CHECK: [[sel_merge]] = OpLabel
+; CHECK: OpBranch [[if_merge]]
+
+; CHECK: [[if_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %c "c"
+ OpDecorate %c Location 0
+ OpDecorate %c DescriptorSet 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+%_ptr_Function_bool = OpTypePointer Function %bool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_UniformConstant_v4float = OpTypePointer UniformConstant %v4float
+ %c = OpVariable %_ptr_UniformConstant_v4float UniformConstant
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_UniformConstant_float = OpTypePointer UniformConstant %float
+ %float_0 = OpConstant %float 0
+ %int_10 = OpConstant %int 10
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %21 = OpAccessChain %_ptr_UniformConstant_float %c %uint_0
+ %22 = OpLoad %float %21
+ %24 = OpFOrdEqual %bool %22 %float_0
+ OpBranch %25
+ %25 = OpLabel
+ %46 = OpPhi %int %int_0 %5 %43 %28
+ %47 = OpPhi %int %int_0 %5 %45 %28
+ OpLoopMerge %27 %28 None
+ OpBranch %29
+ %29 = OpLabel
+ %32 = OpSLessThan %bool %46 %int_10
+ OpBranchConditional %32 %26 %27
+ %26 = OpLabel
+ OpSelectionMerge %35 None
+ OpBranchConditional %24 %34 %39
+ %34 = OpLabel
+ %38 = OpIAdd %int %46 %int_1
+ OpBranch %35
+ %39 = OpLabel
+ %41 = OpIAdd %int %47 %int_1
+ OpBranch %35
+ %35 = OpLabel
+ %48 = OpPhi %int %38 %34 %46 %39
+ %49 = OpPhi %int %47 %34 %41 %39
+ OpBranch %28
+ %28 = OpLabel
+ %43 = OpIAdd %int %48 %int_1
+ %45 = OpIAdd %int %49 %int_1
+ OpBranch %25
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopUnswitchPass>(text, true);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+in vec4 c;
+void main() {
+ int i = 0;
+ bool cond = c[0] == 0;
+ for (; i < 10; i++) {
+ if (cond) {
+ i++;
+ }
+ else {
+ return;
+ }
+ }
+}
+*/
+TEST_F(UnswitchTest, UnswitchExit) {
+ const std::string text = R"(
+; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual
+; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]]
+
+; Loop specialized for false.
+; CHECK: [[loop_f]] = OpLabel
+; CHECK: OpReturn
+
+; Loop specialized for true.
+; CHECK: [[loop_t]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
+; Check that we have i+=2.
+; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; CHECK: [[if_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %c
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %c "c"
+ OpDecorate %c Location 0
+ OpDecorate %23 Uniform
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+%_ptr_Function_bool = OpTypePointer Function %bool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %c = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %float_0 = OpConstant %float 0
+ %int_10 = OpConstant %int 10
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %20 = OpAccessChain %_ptr_Input_float %c %uint_0
+ %21 = OpLoad %float %20
+ %23 = OpFOrdEqual %bool %21 %float_0
+ OpBranch %24
+ %24 = OpLabel
+ %42 = OpPhi %int %int_0 %5 %41 %27
+ OpLoopMerge %26 %27 None
+ OpBranch %28
+ %28 = OpLabel
+ %31 = OpSLessThan %bool %42 %int_10
+ OpBranchConditional %31 %25 %26
+ %25 = OpLabel
+ OpSelectionMerge %34 None
+ OpBranchConditional %23 %33 %38
+ %33 = OpLabel
+ %37 = OpIAdd %int %42 %int_1
+ OpBranch %34
+ %38 = OpLabel
+ OpReturn
+ %34 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %41 = OpIAdd %int %37 %int_1
+ OpBranch %24
+ %26 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopUnswitchPass>(text, true);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+in vec4 c;
+void main() {
+ int i = 0;
+ bool cond = c[0] == 0;
+ for (; i < 10; i++) {
+ if (cond) {
+ continue;
+ }
+ else {
+ i++;
+ }
+ }
+}
+*/
+TEST_F(UnswitchTest, UnswitchContinue) {
+ const std::string text = R"(
+; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual
+; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]]
+
+; Loop specialized for false.
+; CHECK: [[loop_f]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_f]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body:%\w+]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %false
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; Loop specialized for true.
+; CHECK: [[loop_t]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body:%\w+]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %true
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; CHECK: [[if_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %c
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %c "c"
+ OpDecorate %c Location 0
+ OpDecorate %23 Uniform
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+%_ptr_Function_bool = OpTypePointer Function %bool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %c = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %float_0 = OpConstant %float 0
+ %int_10 = OpConstant %int 10
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %20 = OpAccessChain %_ptr_Input_float %c %uint_0
+ %21 = OpLoad %float %20
+ %23 = OpFOrdEqual %bool %21 %float_0
+ OpBranch %24
+ %24 = OpLabel
+ %42 = OpPhi %int %int_0 %5 %41 %27
+ OpLoopMerge %26 %27 None
+ OpBranch %28
+ %28 = OpLabel
+ %31 = OpSLessThan %bool %42 %int_10
+ OpBranchConditional %31 %25 %26
+ %25 = OpLabel
+ OpSelectionMerge %34 None
+ OpBranchConditional %23 %33 %36
+ %33 = OpLabel
+ OpBranch %27
+ %36 = OpLabel
+ %39 = OpIAdd %int %42 %int_1
+ OpBranch %34
+ %34 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %43 = OpPhi %int %42 %33 %39 %34
+ %41 = OpIAdd %int %43 %int_1
+ OpBranch %24
+ %26 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopUnswitchPass>(text, true);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+in vec4 c;
+void main() {
+ int i = 0;
+ bool cond = c[0] == 0;
+ for (; i < 10; i++) {
+ if (cond) {
+ i++;
+ }
+ else {
+ break;
+ }
+ }
+}
+*/
+TEST_F(UnswitchTest, UnswitchKillLoop) {
+ const std::string text = R"(
+; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual
+; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]]
+
+; Loop specialized for false.
+; CHECK: [[loop_f]] = OpLabel
+; CHECK: OpBranch [[if_merge]]
+
+; Loop specialized for true.
+; CHECK: [[loop_t]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_t]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] {{%\w+}} [[merge]]
+; Check that we have i+=2.
+; CHECK: [[phi_i:%\w+]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK: [[iv_i]] = OpIAdd %int [[phi_i]] %int_1
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; CHECK: [[if_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %c
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %c "c"
+ OpDecorate %c Location 0
+ OpDecorate %23 Uniform
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+%_ptr_Function_bool = OpTypePointer Function %bool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %c = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %float_0 = OpConstant %float 0
+ %int_10 = OpConstant %int 10
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %20 = OpAccessChain %_ptr_Input_float %c %uint_0
+ %21 = OpLoad %float %20
+ %23 = OpFOrdEqual %bool %21 %float_0
+ OpBranch %24
+ %24 = OpLabel
+ %42 = OpPhi %int %int_0 %5 %41 %27
+ OpLoopMerge %26 %27 None
+ OpBranch %28
+ %28 = OpLabel
+ %31 = OpSLessThan %bool %42 %int_10
+ OpBranchConditional %31 %25 %26
+ %25 = OpLabel
+ OpSelectionMerge %34 None
+ OpBranchConditional %23 %33 %38
+ %33 = OpLabel
+ %37 = OpIAdd %int %42 %int_1
+ OpBranch %34
+ %38 = OpLabel
+ OpBranch %26
+ %34 = OpLabel
+ OpBranch %27
+ %27 = OpLabel
+ %41 = OpIAdd %int %37 %int_1
+ OpBranch %24
+ %26 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopUnswitchPass>(text, true);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+in vec4 c;
+void main() {
+ int i = 0;
+ int cond = int(c[0]);
+ for (; i < 10; i++) {
+ switch (cond) {
+ case 0:
+ return;
+ case 1:
+ discard;
+ case 2:
+ break;
+ default:
+ break;
+ }
+ }
+ bool cond2 = i == 9;
+}
+*/
+TEST_F(UnswitchTest, UnswitchSwitch) {
+ const std::string text = R"(
+; CHECK: [[cst_cond:%\w+]] = OpConvertFToS
+; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
+; CHECK-NEXT: OpSwitch [[cst_cond]] [[default:%\w+]] 0 [[loop_0:%\w+]] 1 [[loop_1:%\w+]] 2 [[loop_2:%\w+]]
+
+; Loop specialized for 2.
+; CHECK: [[loop_2]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_2]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %int_2
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; Loop specialized for 1.
+; CHECK: [[loop_1]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_1]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %int_1
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; Loop specialized for 0.
+; CHECK: [[loop_0]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[loop_0]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %int_0
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; Loop specialized for the default case.
+; CHECK: [[default]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: [[phi_i:%\w+]] = OpPhi %int %int_0 [[default]] [[iv_i:%\w+]] [[continue:%\w+]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK: [[loop_exit:%\w+]] = OpSLessThan {{%\w+}} [[phi_i]] {{%\w+}}
+; CHECK-NEXT: OpBranchConditional [[loop_exit]] [[loop_body:%\w+]] [[merge]]
+; CHECK: [[loop_body]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpSwitch %uint_3
+; CHECK: [[merge]] = OpLabel
+; CHECK-NEXT: OpBranch [[if_merge]]
+
+; CHECK: [[if_merge]] = OpLabel
+; CHECK-NEXT: OpReturn
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %c
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %c "c"
+ OpDecorate %c Location 0
+ OpDecorate %20 Uniform
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %c = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+%_ptr_Function_bool = OpTypePointer Function %bool
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %18 = OpAccessChain %_ptr_Input_float %c %uint_0
+ %19 = OpLoad %float %18
+ %20 = OpConvertFToS %int %19
+ OpBranch %21
+ %21 = OpLabel
+ %49 = OpPhi %int %int_0 %5 %43 %24
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %29 = OpSLessThan %bool %49 %int_10
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ OpSelectionMerge %35 None
+ OpSwitch %20 %34 0 %31 1 %32 2 %33
+ %34 = OpLabel
+ OpBranch %35
+ %31 = OpLabel
+ OpReturn
+ %32 = OpLabel
+ OpKill
+ %33 = OpLabel
+ OpBranch %35
+ %35 = OpLabel
+ OpBranch %24
+ %24 = OpLabel
+ %43 = OpIAdd %int %49 %int_1
+ OpBranch %21
+ %23 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<LoopUnswitchPass>(text, true);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 440 core
+layout(location = 0)in vec4 c;
+void main() {
+ int i = 0;
+ int j = 0;
+ int k = 0;
+ bool cond = c[0] == 0;
+ for (; i < 10; i++) {
+ for (; j < 10; j++) {
+ if (cond) {
+ i++;
+ } else {
+ j++;
+ }
+ }
+ }
+}
+*/
+TEST_F(UnswitchTest, UnSwitchNested) {
+ // Test that an branch can be unswitched out of two nested loops.
+ const std::string text = R"(
+; CHECK: [[cst_cond:%\w+]] = OpFOrdEqual
+; CHECK-NEXT: OpSelectionMerge [[if_merge:%\w+]] None
+; CHECK-NEXT: OpBranchConditional [[cst_cond]] [[loop_t:%\w+]] [[loop_f:%\w+]]
+
+; Loop specialized for false
+; CHECK: [[loop_f]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_f]] {{%\w+}} [[continue:%\w+]]
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_f]] {{%\w+}} [[continue]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK-NOT: [[merge]] = OpLabel
+; CHECK: OpLoopMerge
+; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: OpSLessThan
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %false
+; CHECK: [[merge]] = OpLabel
+
+; Loop specialized for true. Same as first loop except the branch condition is true.
+; CHECK: [[loop_t]] = OpLabel
+; CHECK-NEXT: OpBranch [[loop:%\w+]]
+; CHECK: [[loop]] = OpLabel
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_t]] {{%\w+}} [[continue:%\w+]]
+; CHECK-NEXT: {{%\w+}} = OpPhi %int %int_0 [[loop_t]] {{%\w+}} [[continue]]
+; CHECK-NEXT: OpLoopMerge [[merge:%\w+]] [[continue]] None
+; CHECK-NOT: [[merge]] = OpLabel
+; CHECK: OpLoopMerge
+; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+; CHECK: [[bb1]] = OpLabel
+; CHECK-NEXT: OpSLessThan
+; CHECK-NEXT: OpBranchConditional {{%\w+}} [[bb2:%\w+]]
+; CHECK: [[bb2]] = OpLabel
+; CHECK-NEXT: OpSelectionMerge
+; CHECK-NEXT: OpBranchConditional %true
+; CHECK: [[merge]] = OpLabel
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %c
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 440
+ OpName %main "main"
+ OpName %c "c"
+ OpDecorate %c Location 0
+ OpDecorate %25 Uniform
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+%_ptr_Function_bool = OpTypePointer Function %bool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %c = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %float_0 = OpConstant %float 0
+ %int_10 = OpConstant %int 10
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %22 = OpAccessChain %_ptr_Input_float %c %uint_0
+ %23 = OpLoad %float %22
+ %25 = OpFOrdEqual %bool %23 %float_0
+ OpBranch %26
+ %26 = OpLabel
+ %67 = OpPhi %int %int_0 %5 %52 %29
+ %68 = OpPhi %int %int_0 %5 %70 %29
+ OpLoopMerge %28 %29 None
+ OpBranch %30
+ %30 = OpLabel
+ %33 = OpSLessThan %bool %67 %int_10
+ OpBranchConditional %33 %27 %28
+ %27 = OpLabel
+ OpBranch %34
+ %34 = OpLabel
+ %69 = OpPhi %int %67 %27 %46 %37
+ %70 = OpPhi %int %68 %27 %50 %37
+ OpLoopMerge %36 %37 None
+ OpBranch %38
+ %38 = OpLabel
+ %40 = OpSLessThan %bool %70 %int_10
+ OpBranchConditional %40 %35 %36
+ %35 = OpLabel
+ OpSelectionMerge %43 None
+ OpBranchConditional %25 %42 %47
+ %42 = OpLabel
+ %46 = OpIAdd %int %69 %int_1
+ OpBranch %43
+ %47 = OpLabel
+ OpReturn
+ %43 = OpLabel
+ OpBranch %37
+ %37 = OpLabel
+ %50 = OpIAdd %int %70 %int_1
+ OpBranch %34
+ %36 = OpLabel
+ OpBranch %29
+ %29 = OpLabel
+ %52 = OpIAdd %int %69 %int_1
+ OpBranch %26
+ %28 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<LoopUnswitchPass>(text, true);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 330 core
+in vec4 c;
+void main() {
+ bool cond = false;
+ if (c[0] == 0) {
+ cond = c[1] == 0;
+ } else {
+ cond = c[2] == 0;
+ }
+ for (int i = 0; i < 10; i++) {
+ if (cond) {
+ i++;
+ }
+ }
+}
+*/
+TEST_F(UnswitchTest, UnswitchNotUniform) {
+ // Check that the unswitch is not triggered (condition loop invariant but not
+ // uniform)
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %c
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 330
+ OpName %main "main"
+ OpName %c "c"
+ OpDecorate %c Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %bool = OpTypeBool
+%_ptr_Function_bool = OpTypePointer Function %bool
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %c = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %float_0 = OpConstant %float 0
+ %uint_1 = OpConstant %uint 1
+ %uint_2 = OpConstant %uint 2
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_0 = OpConstant %int 0
+ %int_10 = OpConstant %int 10
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %17 = OpAccessChain %_ptr_Input_float %c %uint_0
+ %18 = OpLoad %float %17
+ %20 = OpFOrdEqual %bool %18 %float_0
+ OpSelectionMerge %22 None
+ OpBranchConditional %20 %21 %27
+ %21 = OpLabel
+ %24 = OpAccessChain %_ptr_Input_float %c %uint_1
+ %25 = OpLoad %float %24
+ %26 = OpFOrdEqual %bool %25 %float_0
+ OpBranch %22
+ %27 = OpLabel
+ %29 = OpAccessChain %_ptr_Input_float %c %uint_2
+ %30 = OpLoad %float %29
+ %31 = OpFOrdEqual %bool %30 %float_0
+ OpBranch %22
+ %22 = OpLabel
+ %52 = OpPhi %bool %26 %21 %31 %27
+ OpBranch %36
+ %36 = OpLabel
+ %53 = OpPhi %int %int_0 %22 %51 %39
+ OpLoopMerge %38 %39 None
+ OpBranch %40
+ %40 = OpLabel
+ %43 = OpSLessThan %bool %53 %int_10
+ OpBranchConditional %43 %37 %38
+ %37 = OpLabel
+ OpSelectionMerge %46 None
+ OpBranchConditional %52 %45 %46
+ %45 = OpLabel
+ %49 = OpIAdd %int %53 %int_1
+ OpBranch %46
+ %46 = OpLabel
+ %54 = OpPhi %int %53 %37 %49 %45
+ OpBranch %39
+ %39 = OpLabel
+ %51 = OpIAdd %int %54 %int_1
+ OpBranch %36
+ %38 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto result =
+ SinglePassRunAndDisassemble<LoopUnswitchPass>(text, true, false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(UnswitchTest, DontUnswitchLatch) {
+ // Check that the unswitch is not triggered for the latch branch.
+ const std::string text = R"(
+ 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
+%false = OpConstantFalse %bool
+ %4 = OpFunction %void None %3
+ %5 = OpLabel
+ OpBranch %6
+ %6 = OpLabel
+ OpLoopMerge %8 %9 None
+ OpBranch %7
+ %7 = OpLabel
+ OpBranch %9
+ %9 = OpLabel
+ OpBranchConditional %false %6 %8
+ %8 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto result =
+ SinglePassRunAndDisassemble<LoopUnswitchPass>(text, true, false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(UnswitchTest, DontUnswitchConstantCondition) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginLowerLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %bool = OpTypeBool
+ %true = OpConstantTrue %bool
+ %int_1 = OpConstant %int 1
+ %main = OpFunction %void None %4
+ %10 = OpLabel
+ OpBranch %11
+ %11 = OpLabel
+ %12 = OpPhi %int %int_0 %10 %13 %14
+ OpLoopMerge %15 %14 None
+ OpBranch %16
+ %16 = OpLabel
+ %17 = OpSLessThan %bool %12 %int_1
+ OpBranchConditional %17 %18 %15
+ %18 = OpLabel
+ OpSelectionMerge %19 None
+ OpBranchConditional %true %20 %19
+ %20 = OpLabel
+ %21 = OpIAdd %int %12 %int_1
+ OpBranch %19
+ %19 = OpLabel
+ %22 = OpPhi %int %21 %20 %12 %18
+ OpBranch %14
+ %14 = OpLabel
+ %13 = OpIAdd %int %22 %int_1
+ OpBranch %11
+ %15 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto result =
+ SinglePassRunAndDisassemble<LoopUnswitchPass>(text, true, false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/module_test.cpp b/test/opt/module_test.cpp
new file mode 100644
index 0000000..569cf9b
--- /dev/null
+++ b/test/opt/module_test.cpp
@@ -0,0 +1,233 @@
+// Copyright (c) 2016 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 <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/module.h"
+#include "spirv-tools/libspirv.hpp"
+#include "test/opt/module_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::Eq;
+using spvtest::GetIdBound;
+
+TEST(ModuleTest, SetIdBound) {
+ Module m;
+ // It's initialized to 0.
+ EXPECT_EQ(0u, GetIdBound(m));
+
+ m.SetIdBound(19);
+ EXPECT_EQ(19u, GetIdBound(m));
+
+ m.SetIdBound(102);
+ EXPECT_EQ(102u, GetIdBound(m));
+}
+
+// Returns an IRContext owning the module formed by assembling the given text,
+// then loading the result.
+inline std::unique_ptr<IRContext> BuildModule(std::string text) {
+ return spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+}
+
+TEST(ModuleTest, ComputeIdBound) {
+ // Emtpy module case.
+ EXPECT_EQ(1u, BuildModule("")->module()->ComputeIdBound());
+ // Sensitive to result id
+ EXPECT_EQ(2u, BuildModule("%void = OpTypeVoid")->module()->ComputeIdBound());
+ // Sensitive to type id
+ EXPECT_EQ(1000u,
+ BuildModule("%a = OpTypeArray !999 3")->module()->ComputeIdBound());
+ // Sensitive to a regular Id parameter
+ EXPECT_EQ(2000u,
+ BuildModule("OpDecorate !1999 0")->module()->ComputeIdBound());
+ // Sensitive to a scope Id parameter.
+ EXPECT_EQ(3000u,
+ BuildModule("%f = OpFunction %void None %fntype %a = OpLabel "
+ "OpMemoryBarrier !2999 %b\n")
+ ->module()
+ ->ComputeIdBound());
+ // Sensitive to a semantics Id parameter
+ EXPECT_EQ(4000u,
+ BuildModule("%f = OpFunction %void None %fntype %a = OpLabel "
+ "OpMemoryBarrier %b !3999\n")
+ ->module()
+ ->ComputeIdBound());
+}
+
+TEST(ModuleTest, OstreamOperator) {
+ const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %7 "restrict"
+OpDecorate %8 Restrict
+%9 = OpTypeVoid
+%10 = OpTypeInt 32 0
+%11 = OpTypeStruct %10 %10
+%12 = OpTypePointer Function %10
+%13 = OpTypePointer Function %11
+%14 = OpConstant %10 0
+%15 = OpConstant %10 1
+%7 = OpTypeFunction %9
+%1 = OpFunction %9 None %7
+%2 = OpLabel
+%8 = OpVariable %13 Function
+%3 = OpAccessChain %12 %8 %14
+%4 = OpLoad %10 %3
+%5 = OpAccessChain %12 %8 %15
+%6 = OpLoad %10 %5
+OpReturn
+OpFunctionEnd)";
+
+ std::string s;
+ std::ostringstream str(s);
+ str << *BuildModule(text)->module();
+ EXPECT_EQ(text, str.str());
+}
+
+TEST(ModuleTest, OstreamOperatorInt64) {
+ const std::string text = R"(OpCapability Shader
+OpCapability Linkage
+OpCapability Int64
+OpMemoryModel Logical GLSL450
+OpName %7 "restrict"
+OpDecorate %5 Restrict
+%9 = OpTypeVoid
+%10 = OpTypeInt 64 0
+%11 = OpTypeStruct %10 %10
+%12 = OpTypePointer Function %10
+%13 = OpTypePointer Function %11
+%14 = OpConstant %10 0
+%15 = OpConstant %10 1
+%16 = OpConstant %10 4294967297
+%7 = OpTypeFunction %9
+%1 = OpFunction %9 None %7
+%2 = OpLabel
+%5 = OpVariable %12 Function
+%6 = OpLoad %10 %5
+OpSelectionMerge %3 None
+OpSwitch %6 %3 4294967297 %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::string s;
+ std::ostringstream str(s);
+ str << *BuildModule(text)->module();
+ EXPECT_EQ(text, str.str());
+}
+
+TEST(ModuleTest, IdBoundTestAtLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context = BuildModule(text);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound);
+ uint32_t next_id_bound = context->module()->TakeNextIdBound();
+ EXPECT_EQ(next_id_bound, 0);
+ EXPECT_EQ(current_bound, context->module()->id_bound());
+ next_id_bound = context->module()->TakeNextIdBound();
+ EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST(ModuleTest, IdBoundTestBelowLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context = BuildModule(text);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound + 100);
+ uint32_t next_id_bound = context->module()->TakeNextIdBound();
+ EXPECT_EQ(next_id_bound, current_bound);
+ EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+ next_id_bound = context->module()->TakeNextIdBound();
+ EXPECT_EQ(next_id_bound, current_bound + 1);
+}
+
+TEST(ModuleTest, IdBoundTestNearLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4 = OpLabel
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context = BuildModule(text);
+ uint32_t current_bound = context->module()->id_bound();
+ context->set_max_id_bound(current_bound + 1);
+ uint32_t next_id_bound = context->module()->TakeNextIdBound();
+ EXPECT_EQ(next_id_bound, current_bound);
+ EXPECT_EQ(current_bound + 1, context->module()->id_bound());
+ next_id_bound = context->module()->TakeNextIdBound();
+ EXPECT_EQ(next_id_bound, 0);
+}
+
+TEST(ModuleTest, IdBoundTestUIntMax) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeVoid
+%2 = OpTypeFunction %1
+%3 = OpFunction %1 None %2
+%4294967294 = OpLabel ; ID is UINT_MAX-1
+OpReturn
+OpFunctionEnd)";
+
+ std::unique_ptr<IRContext> context = BuildModule(text);
+ uint32_t current_bound = context->module()->id_bound();
+
+ // Expecting |BuildModule| to preserve the numeric ids.
+ EXPECT_EQ(current_bound, std::numeric_limits<uint32_t>::max());
+
+ context->set_max_id_bound(current_bound);
+ uint32_t next_id_bound = context->module()->TakeNextIdBound();
+ EXPECT_EQ(next_id_bound, 0);
+ EXPECT_EQ(current_bound, context->module()->id_bound());
+}
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/module_utils.h b/test/opt/module_utils.h
new file mode 100644
index 0000000..007f132
--- /dev/null
+++ b/test/opt/module_utils.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 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.
+
+#ifndef TEST_OPT_MODULE_UTILS_H_
+#define TEST_OPT_MODULE_UTILS_H_
+
+#include <vector>
+#include "source/opt/module.h"
+
+namespace spvtest {
+
+inline uint32_t GetIdBound(const spvtools::opt::Module& m) {
+ std::vector<uint32_t> binary;
+ m.ToBinary(&binary, false);
+ // The 5-word header must always exist.
+ EXPECT_LE(5u, binary.size());
+ // The bound is the fourth word.
+ return binary[3];
+}
+
+} // namespace spvtest
+
+#endif // TEST_OPT_MODULE_UTILS_H_
diff --git a/test/opt/optimizer_test.cpp b/test/opt/optimizer_test.cpp
new file mode 100644
index 0000000..90abc00
--- /dev/null
+++ b/test/opt/optimizer_test.cpp
@@ -0,0 +1,227 @@
+// 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 <vector>
+
+#include "gmock/gmock.h"
+#include "spirv-tools/libspirv.hpp"
+#include "spirv-tools/optimizer.hpp"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::Eq;
+
+// Return a string that contains the minimum instructions needed to form
+// a valid module. Other instructions can be appended to this string.
+std::string Header() {
+ return R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+)";
+}
+
+TEST(Optimizer, CanRunNullPassWithDistinctInputOutputVectors) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+ std::vector<uint32_t> binary_in;
+ tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid",
+ &binary_in);
+
+ Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+ opt.RegisterPass(CreateNullPass());
+ std::vector<uint32_t> binary_out;
+ opt.Run(binary_in.data(), binary_in.size(), &binary_out);
+
+ std::string disassembly;
+ tools.Disassemble(binary_out.data(), binary_out.size(), &disassembly);
+ EXPECT_THAT(disassembly,
+ Eq(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid\n"));
+}
+
+TEST(Optimizer, CanRunTransformingPassWithDistinctInputOutputVectors) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+ std::vector<uint32_t> binary_in;
+ tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid",
+ &binary_in);
+
+ Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+ opt.RegisterPass(CreateStripDebugInfoPass());
+ std::vector<uint32_t> binary_out;
+ opt.Run(binary_in.data(), binary_in.size(), &binary_out);
+
+ std::string disassembly;
+ tools.Disassemble(binary_out.data(), binary_out.size(), &disassembly);
+ EXPECT_THAT(disassembly, Eq(Header() + "%void = OpTypeVoid\n"));
+}
+
+TEST(Optimizer, CanRunNullPassWithAliasedVectors) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+ std::vector<uint32_t> binary;
+ tools.Assemble("OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary);
+
+ Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+ opt.RegisterPass(CreateNullPass());
+ opt.Run(binary.data(), binary.size(), &binary); // This is the key.
+
+ std::string disassembly;
+ tools.Disassemble(binary.data(), binary.size(), &disassembly);
+ EXPECT_THAT(disassembly, Eq("OpName %foo \"foo\"\n%foo = OpTypeVoid\n"));
+}
+
+TEST(Optimizer, CanRunNullPassWithAliasedVectorDataButDifferentSize) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+ std::vector<uint32_t> binary;
+ tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary);
+
+ Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+ opt.RegisterPass(CreateNullPass());
+ auto orig_size = binary.size();
+ // Now change the size. Add a word that will be ignored
+ // by the optimizer.
+ binary.push_back(42);
+ EXPECT_THAT(orig_size + 1, Eq(binary.size()));
+ opt.Run(binary.data(), orig_size, &binary); // This is the key.
+ // The binary vector should have been rewritten.
+ EXPECT_THAT(binary.size(), Eq(orig_size));
+
+ std::string disassembly;
+ tools.Disassemble(binary.data(), binary.size(), &disassembly);
+ EXPECT_THAT(disassembly,
+ Eq(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid\n"));
+}
+
+TEST(Optimizer, CanRunTransformingPassWithAliasedVectors) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+ std::vector<uint32_t> binary;
+ tools.Assemble(Header() + "OpName %foo \"foo\"\n%foo = OpTypeVoid", &binary);
+
+ Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+ opt.RegisterPass(CreateStripDebugInfoPass());
+ opt.Run(binary.data(), binary.size(), &binary); // This is the key
+
+ std::string disassembly;
+ tools.Disassemble(binary.data(), binary.size(), &disassembly);
+ EXPECT_THAT(disassembly, Eq(Header() + "%void = OpTypeVoid\n"));
+}
+
+TEST(Optimizer, CanValidateFlags) {
+ Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+ EXPECT_FALSE(opt.FlagHasValidForm("bad-flag"));
+ EXPECT_TRUE(opt.FlagHasValidForm("-O"));
+ EXPECT_TRUE(opt.FlagHasValidForm("-Os"));
+ EXPECT_FALSE(opt.FlagHasValidForm("-O2"));
+ EXPECT_TRUE(opt.FlagHasValidForm("--this_flag"));
+}
+
+TEST(Optimizer, CanRegisterPassesFromFlags) {
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+ Optimizer opt(SPV_ENV_UNIVERSAL_1_0);
+
+ spv_message_level_t msg_level;
+ const char* msg_fname;
+ spv_position_t msg_position;
+ const char* msg;
+ auto examine_message = [&msg_level, &msg_fname, &msg_position, &msg](
+ spv_message_level_t ml, const char* f,
+ const spv_position_t& p, const char* m) {
+ msg_level = ml;
+ msg_fname = f;
+ msg_position = p;
+ msg = m;
+ };
+ opt.SetMessageConsumer(examine_message);
+
+ std::vector<std::string> pass_flags = {
+ "--strip-debug",
+ "--strip-reflect",
+ "--set-spec-const-default-value=23:42 21:12",
+ "--if-conversion",
+ "--freeze-spec-const",
+ "--inline-entry-points-exhaustive",
+ "--inline-entry-points-opaque",
+ "--convert-local-access-chains",
+ "--eliminate-dead-code-aggressive",
+ "--eliminate-insert-extract",
+ "--eliminate-local-single-block",
+ "--eliminate-local-single-store",
+ "--merge-blocks",
+ "--merge-return",
+ "--eliminate-dead-branches",
+ "--eliminate-dead-functions",
+ "--eliminate-local-multi-store",
+ "--eliminate-common-uniform",
+ "--eliminate-dead-const",
+ "--eliminate-dead-inserts",
+ "--eliminate-dead-variables",
+ "--fold-spec-const-op-composite",
+ "--loop-unswitch",
+ "--scalar-replacement=300",
+ "--scalar-replacement",
+ "--strength-reduction",
+ "--unify-const",
+ "--flatten-decorations",
+ "--compact-ids",
+ "--cfg-cleanup",
+ "--local-redundancy-elimination",
+ "--loop-invariant-code-motion",
+ "--reduce-load-size",
+ "--redundancy-elimination",
+ "--private-to-local",
+ "--remove-duplicates",
+ "--workaround-1209",
+ "--replace-invalid-opcode",
+ "--simplify-instructions",
+ "--ssa-rewrite",
+ "--copy-propagate-arrays",
+ "--loop-fission=20",
+ "--loop-fusion=2",
+ "--loop-unroll",
+ "--vector-dce",
+ "--loop-unroll-partial=3",
+ "--loop-peeling",
+ "--ccp",
+ "-O",
+ "-Os",
+ "--legalize-hlsl"};
+ EXPECT_TRUE(opt.RegisterPassesFromFlags(pass_flags));
+
+ // Test some invalid flags.
+ EXPECT_FALSE(opt.RegisterPassFromFlag("-O2"));
+ EXPECT_EQ(msg_level, SPV_MSG_ERROR);
+
+ EXPECT_FALSE(opt.RegisterPassFromFlag("-loop-unroll"));
+ EXPECT_EQ(msg_level, SPV_MSG_ERROR);
+
+ EXPECT_FALSE(opt.RegisterPassFromFlag("--set-spec-const-default-value"));
+ EXPECT_EQ(msg_level, SPV_MSG_ERROR);
+
+ EXPECT_FALSE(opt.RegisterPassFromFlag("--scalar-replacement=s"));
+ EXPECT_EQ(msg_level, SPV_MSG_ERROR);
+
+ EXPECT_FALSE(opt.RegisterPassFromFlag("--loop-fission=-4"));
+ EXPECT_EQ(msg_level, SPV_MSG_ERROR);
+
+ EXPECT_FALSE(opt.RegisterPassFromFlag("--loop-fusion=xx"));
+ EXPECT_EQ(msg_level, SPV_MSG_ERROR);
+
+ EXPECT_FALSE(opt.RegisterPassFromFlag("--loop-unroll-partial"));
+ EXPECT_EQ(msg_level, SPV_MSG_ERROR);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
new file mode 100644
index 0000000..117bc31
--- /dev/null
+++ b/test/opt/pass_fixture.h
@@ -0,0 +1,249 @@
+// Copyright (c) 2016 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.
+
+#ifndef TEST_OPT_PASS_FIXTURE_H_
+#define TEST_OPT_PASS_FIXTURE_H_
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "effcee/effcee.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/pass_manager.h"
+#include "source/opt/passes.h"
+#include "source/spirv_validator_options.h"
+#include "source/util/make_unique.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace opt {
+
+// Template class for testing passes. It contains some handy utility methods for
+// running passes and checking results.
+//
+// To write value-Parameterized tests:
+// using ValueParamTest = PassTest<::testing::TestWithParam<std::string>>;
+// To use as normal fixture:
+// using FixtureTest = PassTest<::testing::Test>;
+template <typename TestT>
+class PassTest : public TestT {
+ public:
+ PassTest()
+ : consumer_(
+ [](spv_message_level_t, const char*, const spv_position_t&,
+ const char* message) { std::cerr << message << std::endl; }),
+ context_(nullptr),
+ tools_(SPV_ENV_UNIVERSAL_1_3),
+ manager_(new PassManager()),
+ assemble_options_(SpirvTools::kDefaultAssembleOption),
+ disassemble_options_(SpirvTools::kDefaultDisassembleOption) {}
+
+ // Runs the given |pass| on the binary assembled from the |original|.
+ // Returns a tuple of the optimized binary and the boolean value returned
+ // from pass Process() function.
+ std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary(
+ Pass* pass, const std::string& original, bool skip_nop) {
+ context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer_, original,
+ assemble_options_));
+ EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
+ << original << std::endl;
+ if (!context()) {
+ return std::make_tuple(std::vector<uint32_t>(), Pass::Status::Failure);
+ }
+
+ const auto status = pass->Run(context());
+
+ std::vector<uint32_t> binary;
+ context()->module()->ToBinary(&binary, skip_nop);
+ return std::make_tuple(binary, status);
+ }
+
+ // Runs a single pass of class |PassT| on the binary assembled from the
+ // |assembly|. Returns a tuple of the optimized binary and the boolean value
+ // from the pass Process() function.
+ template <typename PassT, typename... Args>
+ std::tuple<std::vector<uint32_t>, Pass::Status> SinglePassRunToBinary(
+ const std::string& assembly, bool skip_nop, Args&&... args) {
+ auto pass = MakeUnique<PassT>(std::forward<Args>(args)...);
+ pass->SetMessageConsumer(consumer_);
+ return OptimizeToBinary(pass.get(), assembly, skip_nop);
+ }
+
+ // Runs a single pass of class |PassT| on the binary assembled from the
+ // |assembly|, disassembles the optimized binary. Returns a tuple of
+ // disassembly string and the boolean value from the pass Process() function.
+ template <typename PassT, typename... Args>
+ std::tuple<std::string, Pass::Status> SinglePassRunAndDisassemble(
+ const std::string& assembly, bool skip_nop, bool do_validation,
+ Args&&... args) {
+ std::vector<uint32_t> optimized_bin;
+ auto status = Pass::Status::SuccessWithoutChange;
+ std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
+ assembly, skip_nop, std::forward<Args>(args)...);
+ if (do_validation) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
+ spv_result_t error = spvValidateWithOptions(
+ spvContext, ValidatorOptions(), &binary, &diagnostic);
+ EXPECT_EQ(error, 0);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ }
+ std::string optimized_asm;
+ EXPECT_TRUE(
+ tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
+ << "Disassembling failed for shader:\n"
+ << assembly << std::endl;
+ return std::make_tuple(optimized_asm, status);
+ }
+
+ // Runs a single pass of class |PassT| on the binary assembled from the
+ // |original| assembly, and checks whether the optimized binary can be
+ // disassembled to the |expected| assembly. Optionally will also validate
+ // the optimized binary. This does *not* involve pass manager. Callers
+ // are suggested to use SCOPED_TRACE() for better messages.
+ template <typename PassT, typename... Args>
+ void SinglePassRunAndCheck(const std::string& original,
+ const std::string& expected, bool skip_nop,
+ bool do_validation, Args&&... args) {
+ std::vector<uint32_t> optimized_bin;
+ auto status = Pass::Status::SuccessWithoutChange;
+ std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
+ original, skip_nop, std::forward<Args>(args)...);
+ // Check whether the pass returns the correct modification indication.
+ EXPECT_NE(Pass::Status::Failure, status);
+ EXPECT_EQ(original == expected,
+ status == Pass::Status::SuccessWithoutChange);
+ if (do_validation) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
+ spv_result_t error = spvValidateWithOptions(
+ spvContext, ValidatorOptions(), &binary, &diagnostic);
+ EXPECT_EQ(error, 0);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ }
+ std::string optimized_asm;
+ EXPECT_TRUE(
+ tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
+ << "Disassembling failed for shader:\n"
+ << original << std::endl;
+ EXPECT_EQ(expected, optimized_asm);
+ }
+
+ // Runs a single pass of class |PassT| on the binary assembled from the
+ // |original| assembly, and checks whether the optimized binary can be
+ // disassembled to the |expected| assembly. This does *not* involve pass
+ // manager. Callers are suggested to use SCOPED_TRACE() for better messages.
+ template <typename PassT, typename... Args>
+ void SinglePassRunAndCheck(const std::string& original,
+ const std::string& expected, bool skip_nop,
+ Args&&... args) {
+ SinglePassRunAndCheck<PassT>(original, expected, skip_nop, false,
+ std::forward<Args>(args)...);
+ }
+
+ // Runs a single pass of class |PassT| on the binary assembled from the
+ // |original| assembly, then runs an Effcee matcher over the disassembled
+ // result, using checks parsed from |original|. Always skips OpNop.
+ // This does *not* involve pass manager. Callers are suggested to use
+ // SCOPED_TRACE() for better messages.
+ template <typename PassT, typename... Args>
+ void SinglePassRunAndMatch(const std::string& original, bool do_validation,
+ Args&&... args) {
+ const bool skip_nop = true;
+ auto pass_result = SinglePassRunAndDisassemble<PassT>(
+ original, skip_nop, do_validation, std::forward<Args>(args)...);
+ auto disassembly = std::get<0>(pass_result);
+ auto match_result = effcee::Match(disassembly, original);
+ EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+ << match_result.message() << "\nChecking result:\n"
+ << disassembly;
+ }
+
+ // Adds a pass to be run.
+ template <typename PassT, typename... Args>
+ void AddPass(Args&&... args) {
+ manager_->AddPass<PassT>(std::forward<Args>(args)...);
+ }
+
+ // Renews the pass manager, including clearing all previously added passes.
+ void RenewPassManger() {
+ manager_ = MakeUnique<PassManager>();
+ manager_->SetMessageConsumer(consumer_);
+ }
+
+ // Runs the passes added thus far using a pass manager on the binary assembled
+ // from the |original| assembly, and checks whether the optimized binary can
+ // be disassembled to the |expected| assembly. Callers are suggested to use
+ // SCOPED_TRACE() for better messages.
+ void RunAndCheck(const std::string& original, const std::string& expected) {
+ assert(manager_->NumPasses());
+
+ context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, original,
+ assemble_options_));
+ ASSERT_NE(nullptr, context());
+
+ manager_->Run(context());
+
+ std::vector<uint32_t> binary;
+ context()->module()->ToBinary(&binary, /* skip_nop = */ false);
+
+ std::string optimized;
+ EXPECT_TRUE(tools_.Disassemble(binary, &optimized, disassemble_options_));
+ EXPECT_EQ(expected, optimized);
+ }
+
+ void SetAssembleOptions(uint32_t assemble_options) {
+ assemble_options_ = assemble_options;
+ }
+
+ void SetDisassembleOptions(uint32_t disassemble_options) {
+ disassemble_options_ = disassemble_options;
+ }
+
+ MessageConsumer consumer() { return consumer_; }
+ IRContext* context() { return context_.get(); }
+
+ void SetMessageConsumer(MessageConsumer msg_consumer) {
+ consumer_ = msg_consumer;
+ }
+
+ spv_validator_options ValidatorOptions() { return &validator_options_; }
+
+ private:
+ MessageConsumer consumer_; // Message consumer.
+ std::unique_ptr<IRContext> context_; // IR context
+ SpirvTools tools_; // An instance for calling SPIRV-Tools functionalities.
+ std::unique_ptr<PassManager> manager_; // The pass manager.
+ uint32_t assemble_options_;
+ uint32_t disassemble_options_;
+ spv_validator_options_t validator_options_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // TEST_OPT_PASS_FIXTURE_H_
diff --git a/test/opt/pass_manager_test.cpp b/test/opt/pass_manager_test.cpp
new file mode 100644
index 0000000..22d5e22
--- /dev/null
+++ b/test/opt/pass_manager_test.cpp
@@ -0,0 +1,191 @@
+// Copyright (c) 2016 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 <initializer_list>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/util/make_unique.h"
+#include "test/opt/module_utils.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using spvtest::GetIdBound;
+using ::testing::Eq;
+
+// A null pass whose construtors accept arguments
+class NullPassWithArgs : public NullPass {
+ public:
+ NullPassWithArgs(uint32_t) {}
+ NullPassWithArgs(std::string) {}
+ NullPassWithArgs(const std::vector<int>&) {}
+ NullPassWithArgs(const std::vector<int>&, uint32_t) {}
+
+ const char* name() const override { return "null-with-args"; }
+};
+
+TEST(PassManager, Interface) {
+ PassManager manager;
+ EXPECT_EQ(0u, manager.NumPasses());
+
+ manager.AddPass<StripDebugInfoPass>();
+ EXPECT_EQ(1u, manager.NumPasses());
+ EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+
+ manager.AddPass(MakeUnique<NullPass>());
+ EXPECT_EQ(2u, manager.NumPasses());
+ EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+ EXPECT_STREQ("null", manager.GetPass(1)->name());
+
+ manager.AddPass<StripDebugInfoPass>();
+ EXPECT_EQ(3u, manager.NumPasses());
+ EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+ EXPECT_STREQ("null", manager.GetPass(1)->name());
+ EXPECT_STREQ("strip-debug", manager.GetPass(2)->name());
+
+ manager.AddPass<NullPassWithArgs>(1u);
+ manager.AddPass<NullPassWithArgs>("null pass args");
+ manager.AddPass<NullPassWithArgs>(std::initializer_list<int>{1, 2});
+ manager.AddPass<NullPassWithArgs>(std::initializer_list<int>{1, 2}, 3);
+ EXPECT_EQ(7u, manager.NumPasses());
+ EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
+ EXPECT_STREQ("null", manager.GetPass(1)->name());
+ EXPECT_STREQ("strip-debug", manager.GetPass(2)->name());
+ EXPECT_STREQ("null-with-args", manager.GetPass(3)->name());
+ EXPECT_STREQ("null-with-args", manager.GetPass(4)->name());
+ EXPECT_STREQ("null-with-args", manager.GetPass(5)->name());
+ EXPECT_STREQ("null-with-args", manager.GetPass(6)->name());
+}
+
+// A pass that appends an OpNop instruction to the debug1 section.
+class AppendOpNopPass : public Pass {
+ public:
+ const char* name() const override { return "AppendOpNop"; }
+ Status Process() override {
+ context()->AddDebug1Inst(MakeUnique<Instruction>(context()));
+ return Status::SuccessWithChange;
+ }
+};
+
+// A pass that appends specified number of OpNop instructions to the debug1
+// section.
+class AppendMultipleOpNopPass : public Pass {
+ public:
+ explicit AppendMultipleOpNopPass(uint32_t num_nop) : num_nop_(num_nop) {}
+
+ const char* name() const override { return "AppendOpNop"; }
+ Status Process() override {
+ for (uint32_t i = 0; i < num_nop_; i++) {
+ context()->AddDebug1Inst(MakeUnique<Instruction>(context()));
+ }
+ return Status::SuccessWithChange;
+ }
+
+ private:
+ uint32_t num_nop_;
+};
+
+// A pass that duplicates the last instruction in the debug1 section.
+class DuplicateInstPass : public Pass {
+ public:
+ const char* name() const override { return "DuplicateInst"; }
+ Status Process() override {
+ auto inst = MakeUnique<Instruction>(*(--context()->debug1_end()));
+ context()->AddDebug1Inst(std::move(inst));
+ return Status::SuccessWithChange;
+ }
+};
+
+using PassManagerTest = PassTest<::testing::Test>;
+
+TEST_F(PassManagerTest, Run) {
+ const std::string text = "OpMemoryModel Logical GLSL450\nOpSource ESSL 310\n";
+
+ AddPass<AppendOpNopPass>();
+ AddPass<AppendOpNopPass>();
+ RunAndCheck(text, text + "OpNop\nOpNop\n");
+
+ RenewPassManger();
+ AddPass<AppendOpNopPass>();
+ AddPass<DuplicateInstPass>();
+ RunAndCheck(text, text + "OpNop\nOpNop\n");
+
+ RenewPassManger();
+ AddPass<DuplicateInstPass>();
+ AddPass<AppendOpNopPass>();
+ RunAndCheck(text, text + "OpSource ESSL 310\nOpNop\n");
+
+ RenewPassManger();
+ AddPass<AppendMultipleOpNopPass>(3);
+ RunAndCheck(text, text + "OpNop\nOpNop\nOpNop\n");
+}
+
+// A pass that appends an OpTypeVoid instruction that uses a given id.
+class AppendTypeVoidInstPass : public Pass {
+ public:
+ explicit AppendTypeVoidInstPass(uint32_t result_id) : result_id_(result_id) {}
+
+ const char* name() const override { return "AppendTypeVoidInstPass"; }
+ Status Process() override {
+ auto inst = MakeUnique<Instruction>(context(), SpvOpTypeVoid, 0, result_id_,
+ std::vector<Operand>{});
+ context()->AddType(std::move(inst));
+ return Status::SuccessWithChange;
+ }
+
+ private:
+ uint32_t result_id_;
+};
+
+TEST(PassManager, RecomputeIdBoundAutomatically) {
+ PassManager manager;
+ std::unique_ptr<Module> module(new Module());
+ IRContext context(SPV_ENV_UNIVERSAL_1_2, std::move(module),
+ manager.consumer());
+ EXPECT_THAT(GetIdBound(*context.module()), Eq(0u));
+
+ manager.Run(&context);
+ manager.AddPass<AppendOpNopPass>();
+ // With no ID changes, the ID bound does not change.
+ EXPECT_THAT(GetIdBound(*context.module()), Eq(0u));
+
+ // Now we force an Id of 100 to be used.
+ manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(100));
+ EXPECT_THAT(GetIdBound(*context.module()), Eq(0u));
+ manager.Run(&context);
+ // The Id has been updated automatically, even though the pass
+ // did not update it.
+ EXPECT_THAT(GetIdBound(*context.module()), Eq(101u));
+
+ // Try one more time!
+ manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(200));
+ manager.Run(&context);
+ EXPECT_THAT(GetIdBound(*context.module()), Eq(201u));
+
+ // Add another pass, but which uses a lower Id.
+ manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(10));
+ manager.Run(&context);
+ // The Id stays high.
+ EXPECT_THAT(GetIdBound(*context.module()), Eq(201u));
+}
+
+} // anonymous namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/pass_merge_return_test.cpp b/test/opt/pass_merge_return_test.cpp
new file mode 100644
index 0000000..9c8d80d
--- /dev/null
+++ b/test/opt/pass_merge_return_test.cpp
@@ -0,0 +1,1373 @@
+// 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: OpLoopMerge [[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: OpLoopMerge [[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: OpLoopMerge [[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: OpLoopMerge [[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: OpLoopMerge [[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: OpLoopMerge [[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: 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: 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 [[val]] [[bb]] {{%\w+}} [[old_ret_block]]
+; 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: OpLoopMerge
+ ; CHECK: OpLoopMerge [[merge:%\w+]] [[continue:%\w+]]
+ ; CHECK: [[continue]] = OpLabel
+ ; CHECK-NEXT: [[undef:%\w+]] = OpUndef
+ ; CHECK: [[merge]] = OpLabel
+ ; CHECK-NEXT: [[phi:%\w+]] = OpPhi %bool [[undef]] [[continue]] {{%\w+}} {{%\w+}}
+ ; 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, InnerLoopMergeIsOuterLoopContinue) {
+ const std::string before =
+ R"(
+ ; CHECK: OpLoopMerge
+ ; CHECK-NEXT: OpBranch [[bb1:%\w+]]
+ ; CHECK: [[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);
+}
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/pass_remove_duplicates_test.cpp b/test/opt/pass_remove_duplicates_test.cpp
new file mode 100644
index 0000000..887fdfd
--- /dev/null
+++ b/test/opt/pass_remove_duplicates_test.cpp
@@ -0,0 +1,646 @@
+// Copyright (c) 2017 Pierre Moreau
+//
+// 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 <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/pass_manager.h"
+#include "source/opt/remove_duplicates_pass.h"
+#include "source/spirv_constant.h"
+#include "test/unit_spirv.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+class RemoveDuplicatesTest : public ::testing::Test {
+ public:
+ RemoveDuplicatesTest()
+ : tools_(SPV_ENV_UNIVERSAL_1_2),
+ context_(),
+ consumer_([this](spv_message_level_t level, const char*,
+ const spv_position_t& position, const char* message) {
+ if (!error_message_.empty()) error_message_ += "\n";
+ switch (level) {
+ case SPV_MSG_FATAL:
+ case SPV_MSG_INTERNAL_ERROR:
+ case SPV_MSG_ERROR:
+ error_message_ += "ERROR";
+ break;
+ case SPV_MSG_WARNING:
+ error_message_ += "WARNING";
+ break;
+ case SPV_MSG_INFO:
+ error_message_ += "INFO";
+ break;
+ case SPV_MSG_DEBUG:
+ error_message_ += "DEBUG";
+ break;
+ }
+ error_message_ +=
+ ": " + std::to_string(position.index) + ": " + message;
+ }),
+ disassemble_options_(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER),
+ error_message_() {
+ tools_.SetMessageConsumer(consumer_);
+ }
+
+ void TearDown() override { error_message_.clear(); }
+
+ std::string RunPass(const std::string& text) {
+ context_ = spvtools::BuildModule(SPV_ENV_UNIVERSAL_1_2, consumer_, text);
+ if (!context_.get()) return std::string();
+
+ PassManager manager;
+ manager.SetMessageConsumer(consumer_);
+ manager.AddPass<RemoveDuplicatesPass>();
+
+ Pass::Status pass_res = manager.Run(context_.get());
+ if (pass_res == Pass::Status::Failure) return std::string();
+
+ return ModuleToText();
+ }
+
+ // Disassembles |binary| and outputs the result in |text|. If |text| is a
+ // null pointer, SPV_ERROR_INVALID_POINTER is returned.
+ spv_result_t Disassemble(const std::vector<uint32_t>& binary,
+ std::string* text) {
+ if (!text) return SPV_ERROR_INVALID_POINTER;
+ return tools_.Disassemble(binary, text, disassemble_options_)
+ ? SPV_SUCCESS
+ : SPV_ERROR_INVALID_BINARY;
+ }
+
+ // Returns the accumulated error messages for the test.
+ std::string GetErrorMessage() const { return error_message_; }
+
+ std::string ToText(const std::vector<Instruction*>& inst) {
+ std::vector<uint32_t> binary = {SpvMagicNumber, 0x10200, 0u, 2u, 0u};
+ for (const Instruction* i : inst)
+ i->ToBinaryWithoutAttachedDebugInsts(&binary);
+ std::string text;
+ Disassemble(binary, &text);
+ return text;
+ }
+
+ std::string ModuleToText() {
+ std::vector<uint32_t> binary;
+ context_->module()->ToBinary(&binary, false);
+ std::string text;
+ Disassemble(binary, &text);
+ return text;
+ }
+
+ private:
+ spvtools::SpirvTools
+ tools_; // An instance for calling SPIRV-Tools functionalities.
+ std::unique_ptr<IRContext> context_;
+ spvtools::MessageConsumer consumer_;
+ uint32_t disassemble_options_;
+ std::string error_message_;
+};
+
+TEST_F(RemoveDuplicatesTest, DuplicateCapabilities) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+TEST_F(RemoveDuplicatesTest, DuplicateExtInstImports) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "OpenCL.std"
+%2 = OpExtInstImport "OpenCL.std"
+%3 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "OpenCL.std"
+%3 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+TEST_F(RemoveDuplicatesTest, DuplicateTypes) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeInt 32 0
+%3 = OpTypeStruct %1 %2
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%3 = OpTypeStruct %1 %1
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+TEST_F(RemoveDuplicatesTest, SameTypeDifferentMemberDecoration) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 GLSLPacked
+%2 = OpTypeInt 32 0
+%1 = OpTypeStruct %2 %2
+%3 = OpTypeStruct %2 %2
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 GLSLPacked
+%2 = OpTypeInt 32 0
+%1 = OpTypeStruct %2 %2
+%3 = OpTypeStruct %2 %2
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+TEST_F(RemoveDuplicatesTest, SameTypeAndMemberDecoration) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 GLSLPacked
+OpDecorate %2 GLSLPacked
+%3 = OpTypeInt 32 0
+%1 = OpTypeStruct %3 %3
+%2 = OpTypeStruct %3 %3
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 GLSLPacked
+%3 = OpTypeInt 32 0
+%1 = OpTypeStruct %3 %3
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+TEST_F(RemoveDuplicatesTest, SameTypeAndDifferentName) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %1 "Type1"
+OpName %2 "Type2"
+%3 = OpTypeInt 32 0
+%1 = OpTypeStruct %3 %3
+%2 = OpTypeStruct %3 %3
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %1 "Type1"
+%3 = OpTypeInt 32 0
+%1 = OpTypeStruct %3 %3
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+// Check that #1033 has been fixed.
+TEST_F(RemoveDuplicatesTest, DoNotRemoveDifferentOpDecorationGroup) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+%1 = OpDecorationGroup
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %3 %1 %2
+%4 = OpTypeInt 32 0
+%3 = OpVariable %4 Uniform
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+%1 = OpDecorationGroup
+OpDecorate %2 Restrict
+%2 = OpDecorationGroup
+OpGroupDecorate %3 %1 %2
+%4 = OpTypeInt 32 0
+%3 = OpVariable %4 Uniform
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+TEST_F(RemoveDuplicatesTest, DifferentDecorationGroup) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %1 Restrict
+%1 = OpDecorationGroup
+OpDecorate %2 Constant
+%2 = OpDecorationGroup
+OpGroupDecorate %1 %3
+OpGroupDecorate %2 %4
+%5 = OpTypeInt 32 0
+%3 = OpVariable %5 Uniform
+%4 = OpVariable %5 Uniform
+)";
+ const std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %1 Constant
+OpDecorate %1 Restrict
+%1 = OpDecorationGroup
+OpDecorate %2 Constant
+%2 = OpDecorationGroup
+OpGroupDecorate %1 %3
+OpGroupDecorate %2 %4
+%5 = OpTypeInt 32 0
+%3 = OpVariable %5 Uniform
+%4 = OpVariable %5 Uniform
+)";
+
+ EXPECT_EQ(RunPass(spirv), after);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+// Test what happens when a type is a resource type. For now we are merging
+// them, but, if we want to merge types and make reflection work (issue #1372),
+// we will not be able to merge %2 and %3 below.
+TEST_F(RemoveDuplicatesTest, DontMergeNestedResourceTypes) {
+ const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %2 "NormalAdjust"
+OpMemberName %2 0 "XDir"
+OpMemberName %3 0 "AdjustXYZ"
+OpMemberName %3 1 "AdjustDir"
+OpName %4 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %2 0 Offset 0
+OpMemberDecorate %3 0 Offset 0
+OpMemberDecorate %3 1 Offset 16
+OpDecorate %3 Block
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+%5 = OpTypeFloat 32
+%6 = OpTypeVector %5 3
+%1 = OpTypeStruct %6
+%2 = OpTypeStruct %6
+%3 = OpTypeStruct %1 %2
+%7 = OpTypePointer Uniform %3
+%4 = OpVariable %7 Uniform
+)";
+
+ const std::string result = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpMemberName %3 0 "AdjustXYZ"
+OpMemberName %3 1 "AdjustDir"
+OpName %4 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %3 0 Offset 0
+OpMemberDecorate %3 1 Offset 16
+OpDecorate %3 Block
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+%5 = OpTypeFloat 32
+%6 = OpTypeVector %5 3
+%1 = OpTypeStruct %6
+%3 = OpTypeStruct %1 %1
+%7 = OpTypePointer Uniform %3
+%4 = OpVariable %7 Uniform
+)";
+
+ EXPECT_EQ(RunPass(spirv), result);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+// See comment for DontMergeNestedResourceTypes.
+TEST_F(RemoveDuplicatesTest, DontMergeResourceTypes) {
+ const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %2 "NormalAdjust"
+OpMemberName %2 0 "XDir"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %2 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+OpDecorate %4 DescriptorSet 1
+OpDecorate %4 Binding 0
+%5 = OpTypeFloat 32
+%6 = OpTypeVector %5 3
+%1 = OpTypeStruct %6
+%2 = OpTypeStruct %6
+%7 = OpTypePointer Uniform %1
+%8 = OpTypePointer Uniform %2
+%3 = OpVariable %7 Uniform
+%4 = OpVariable %8 Uniform
+)";
+
+ const std::string result = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+OpDecorate %4 DescriptorSet 1
+OpDecorate %4 Binding 0
+%5 = OpTypeFloat 32
+%6 = OpTypeVector %5 3
+%1 = OpTypeStruct %6
+%7 = OpTypePointer Uniform %1
+%3 = OpVariable %7 Uniform
+%4 = OpVariable %7 Uniform
+)";
+
+ EXPECT_EQ(RunPass(spirv), result);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+// See comment for DontMergeNestedResourceTypes.
+TEST_F(RemoveDuplicatesTest, DontMergeResourceTypesContainingArray) {
+ const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %2 "NormalAdjust"
+OpMemberName %2 0 "XDir"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %2 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+OpDecorate %4 DescriptorSet 1
+OpDecorate %4 Binding 0
+%5 = OpTypeFloat 32
+%6 = OpTypeVector %5 3
+%1 = OpTypeStruct %6
+%2 = OpTypeStruct %6
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 4
+%9 = OpTypeArray %1 %8
+%10 = OpTypeArray %2 %8
+%11 = OpTypePointer Uniform %9
+%12 = OpTypePointer Uniform %10
+%3 = OpVariable %11 Uniform
+%4 = OpVariable %12 Uniform
+)";
+
+ const std::string result = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+OpDecorate %4 DescriptorSet 1
+OpDecorate %4 Binding 0
+%5 = OpTypeFloat 32
+%6 = OpTypeVector %5 3
+%1 = OpTypeStruct %6
+%7 = OpTypeInt 32 0
+%8 = OpConstant %7 4
+%9 = OpTypeArray %1 %8
+%11 = OpTypePointer Uniform %9
+%3 = OpVariable %11 Uniform
+%4 = OpVariable %11 Uniform
+)";
+
+ EXPECT_EQ(RunPass(spirv), result);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+// Test that we merge the type of a resource with a type that is not the type
+// a resource. The resource type appears first in this case. We must keep
+// the resource type.
+TEST_F(RemoveDuplicatesTest, MergeResourceTypeWithNonresourceType1) {
+ const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %2 "NormalAdjust"
+OpMemberName %2 0 "XDir"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %2 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+%4 = OpTypeFloat 32
+%5 = OpTypeVector %4 3
+%1 = OpTypeStruct %5
+%2 = OpTypeStruct %5
+%6 = OpTypePointer Uniform %1
+%7 = OpTypePointer Uniform %2
+%3 = OpVariable %6 Uniform
+%8 = OpVariable %7 Uniform
+)";
+
+ const std::string result = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+%4 = OpTypeFloat 32
+%5 = OpTypeVector %4 3
+%1 = OpTypeStruct %5
+%6 = OpTypePointer Uniform %1
+%3 = OpVariable %6 Uniform
+%8 = OpVariable %6 Uniform
+)";
+
+ EXPECT_EQ(RunPass(spirv), result);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+// Test that we merge the type of a resource with a type that is not the type
+// a resource. The resource type appears second in this case. We must keep
+// the resource type.
+//
+// See comment for DontMergeNestedResourceTypes.
+TEST_F(RemoveDuplicatesTest, MergeResourceTypeWithNonresourceType2) {
+ const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %2 "NormalAdjust"
+OpMemberName %2 0 "XDir"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpMemberDecorate %2 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+%4 = OpTypeFloat 32
+%5 = OpTypeVector %4 3
+%1 = OpTypeStruct %5
+%2 = OpTypeStruct %5
+%6 = OpTypePointer Uniform %1
+%7 = OpTypePointer Uniform %2
+%8 = OpVariable %6 Uniform
+%3 = OpVariable %7 Uniform
+)";
+
+ const std::string result = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpSource HLSL 600
+OpName %1 "PositionAdjust"
+OpMemberName %1 0 "XAdjust"
+OpName %3 "Constants"
+OpMemberDecorate %1 0 Offset 0
+OpDecorate %3 DescriptorSet 0
+OpDecorate %3 Binding 0
+%4 = OpTypeFloat 32
+%5 = OpTypeVector %4 3
+%1 = OpTypeStruct %5
+%6 = OpTypePointer Uniform %1
+%8 = OpVariable %6 Uniform
+%3 = OpVariable %6 Uniform
+)";
+
+ EXPECT_EQ(RunPass(spirv), result);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+// In this test, %8 and %9 are the same and only %9 is used in a resource.
+// However, we cannot merge them unless we also merge %2 and %3, which cannot
+// happen because both are used in resources.
+//
+// If we try to avoid replaces resource types, then remove duplicates should
+// have not change in this case. That is not currently implemented.
+TEST_F(RemoveDuplicatesTest, MergeResourceTypeWithNonresourceType3) {
+ const std::string spirv = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource HLSL 600
+OpName %2 "PositionAdjust"
+OpMemberName %2 0 "XAdjust"
+OpName %3 "NormalAdjust"
+OpMemberName %3 0 "XDir"
+OpName %4 "Constants"
+OpMemberDecorate %2 0 Offset 0
+OpMemberDecorate %3 0 Offset 0
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+OpDecorate %5 DescriptorSet 1
+OpDecorate %5 Binding 0
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 3
+%2 = OpTypeStruct %7
+%3 = OpTypeStruct %7
+%8 = OpTypePointer Uniform %3
+%9 = OpTypePointer Uniform %2
+%10 = OpTypeStruct %3
+%11 = OpTypePointer Uniform %10
+%5 = OpVariable %9 Uniform
+%4 = OpVariable %11 Uniform
+%12 = OpTypeVoid
+%13 = OpTypeFunction %12
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 0
+%1 = OpFunction %12 None %13
+%16 = OpLabel
+%17 = OpAccessChain %8 %4 %15
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string result = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %1 "main"
+OpSource HLSL 600
+OpName %2 "PositionAdjust"
+OpMemberName %2 0 "XAdjust"
+OpName %4 "Constants"
+OpMemberDecorate %2 0 Offset 0
+OpDecorate %4 DescriptorSet 0
+OpDecorate %4 Binding 0
+OpDecorate %5 DescriptorSet 1
+OpDecorate %5 Binding 0
+%6 = OpTypeFloat 32
+%7 = OpTypeVector %6 3
+%2 = OpTypeStruct %7
+%8 = OpTypePointer Uniform %2
+%10 = OpTypeStruct %2
+%11 = OpTypePointer Uniform %10
+%5 = OpVariable %8 Uniform
+%4 = OpVariable %11 Uniform
+%12 = OpTypeVoid
+%13 = OpTypeFunction %12
+%14 = OpTypeInt 32 0
+%15 = OpConstant %14 0
+%1 = OpFunction %12 None %13
+%16 = OpLabel
+%17 = OpAccessChain %8 %4 %15
+OpReturn
+OpFunctionEnd
+)";
+
+ EXPECT_EQ(RunPass(spirv), result);
+ EXPECT_EQ(GetErrorMessage(), "");
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/pass_utils.cpp b/test/opt/pass_utils.cpp
new file mode 100644
index 0000000..4709d0f
--- /dev/null
+++ b/test/opt/pass_utils.cpp
@@ -0,0 +1,102 @@
+// Copyright (c) 2016 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 "test/opt/pass_utils.h"
+
+#include <algorithm>
+#include <sstream>
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+// Well, this is another place requiring the knowledge of the grammar and can be
+// stale when SPIR-V is updated. It would be nice to automatically generate
+// this, but the cost is just too high.
+
+const char* kDebugOpcodes[] = {
+ // clang-format off
+ "OpSourceContinued", "OpSource", "OpSourceExtension",
+ "OpName", "OpMemberName", "OpString",
+ "OpLine", "OpNoLine", "OpModuleProcessed"
+ // clang-format on
+};
+
+} // anonymous namespace
+
+MessageConsumer GetTestMessageConsumer(
+ std::vector<Message>& expected_messages) {
+ return [&expected_messages](spv_message_level_t level, const char* source,
+ const spv_position_t& position,
+ const char* message) {
+ EXPECT_TRUE(!expected_messages.empty());
+ if (expected_messages.empty()) {
+ return;
+ }
+
+ EXPECT_EQ(expected_messages[0].level, level);
+ EXPECT_EQ(expected_messages[0].line_number, position.line);
+ EXPECT_EQ(expected_messages[0].column_number, position.column);
+ EXPECT_STREQ(expected_messages[0].source_file, source);
+ EXPECT_STREQ(expected_messages[0].message, message);
+
+ expected_messages.erase(expected_messages.begin());
+ };
+}
+
+bool FindAndReplace(std::string* process_str, const std::string find_str,
+ const std::string replace_str) {
+ if (process_str->empty() || find_str.empty()) {
+ return false;
+ }
+ bool replaced = false;
+ // Note this algorithm has quadratic time complexity. It is OK for test cases
+ // with short strings, but might not fit in other contexts.
+ for (size_t pos = process_str->find(find_str, 0); pos != std::string::npos;
+ pos = process_str->find(find_str, pos)) {
+ process_str->replace(pos, find_str.length(), replace_str);
+ pos += replace_str.length();
+ replaced = true;
+ }
+ return replaced;
+}
+
+bool ContainsDebugOpcode(const char* inst) {
+ return std::any_of(std::begin(kDebugOpcodes), std::end(kDebugOpcodes),
+ [inst](const char* op) {
+ return std::string(inst).find(op) != std::string::npos;
+ });
+}
+
+std::string SelectiveJoin(const std::vector<const char*>& strings,
+ const std::function<bool(const char*)>& skip_dictator,
+ char delimiter) {
+ std::ostringstream oss;
+ for (const auto* str : strings) {
+ if (!skip_dictator(str)) oss << str << delimiter;
+ }
+ return oss.str();
+}
+
+std::string JoinAllInsts(const std::vector<const char*>& insts) {
+ return SelectiveJoin(insts, [](const char*) { return false; });
+}
+
+std::string JoinNonDebugInsts(const std::vector<const char*>& insts) {
+ return SelectiveJoin(
+ insts, [](const char* inst) { return ContainsDebugOpcode(inst); });
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/pass_utils.h b/test/opt/pass_utils.h
new file mode 100644
index 0000000..8968f8a
--- /dev/null
+++ b/test/opt/pass_utils.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2016 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.
+
+#ifndef TEST_OPT_PASS_UTILS_H_
+#define TEST_OPT_PASS_UTILS_H_
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "include/spirv-tools/libspirv.h"
+#include "include/spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace opt {
+
+struct Message {
+ spv_message_level_t level;
+ const char* source_file;
+ uint32_t line_number;
+ uint32_t column_number;
+ const char* message;
+};
+
+// Return a message consumer that can be used to check that the message produced
+// are the messages in |expexted_messages|, and in the same order.
+MessageConsumer GetTestMessageConsumer(std::vector<Message>& expected_messages);
+
+// In-place substring replacement. Finds the |find_str| in the |process_str|
+// and replaces the found substring with |replace_str|. Returns true if at
+// least one replacement is done successfully, returns false otherwise. The
+// replaced substring won't be processed again, which means: If the
+// |replace_str| has |find_str| as its substring, that newly replaced part of
+// |process_str| won't be processed again.
+bool FindAndReplace(std::string* process_str, const std::string find_str,
+ const std::string replace_str);
+
+// Returns true if the given string contains any debug opcode substring.
+bool ContainsDebugOpcode(const char* inst);
+
+// Returns the concatenated string from a vector of |strings|, with postfixing
+// each string with the given |delimiter|. if the |skip_dictator| returns true
+// for an original string, that string will be omitted.
+std::string SelectiveJoin(const std::vector<const char*>& strings,
+ const std::function<bool(const char*)>& skip_dictator,
+ char delimiter = '\n');
+
+// Concatenates a vector of strings into one string. Each string is postfixed
+// with '\n'.
+std::string JoinAllInsts(const std::vector<const char*>& insts);
+
+// Concatenates a vector of strings into one string. Each string is postfixed
+// with '\n'. If a string contains opcode for debug instruction, that string
+// will be ignored.
+std::string JoinNonDebugInsts(const std::vector<const char*>& insts);
+
+// Returns a vector that contains the contents of |a| followed by the contents
+// of |b|.
+template <typename T>
+std::vector<T> Concat(const std::vector<T>& a, const std::vector<T>& b) {
+ std::vector<T> ret;
+ std::copy(a.begin(), a.end(), back_inserter(ret));
+ std::copy(b.begin(), b.end(), back_inserter(ret));
+ return ret;
+}
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // TEST_OPT_PASS_UTILS_H_
diff --git a/test/opt/pch_test_opt.cpp b/test/opt/pch_test_opt.cpp
new file mode 100644
index 0000000..f158129
--- /dev/null
+++ b/test/opt/pch_test_opt.cpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2018 The Khronos Group 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 "pch_test_opt.h"
diff --git a/test/opt/pch_test_opt.h b/test/opt/pch_test_opt.h
new file mode 100644
index 0000000..4e8106f
--- /dev/null
+++ b/test/opt/pch_test_opt.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2018 The Khronos Group 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 "gmock/gmock.h"
+#include "source/opt/iterator.h"
+#include "source/opt/loop_dependence.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "source/opt/scalar_analysis.h"
+#include "source/opt/tree_iterator.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
diff --git a/test/opt/private_to_local_test.cpp b/test/opt/private_to_local_test.cpp
new file mode 100644
index 0000000..3ec74fa
--- /dev/null
+++ b/test/opt/private_to_local_test.cpp
@@ -0,0 +1,313 @@
+// 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 "source/opt/build_module.h"
+#include "source/opt/value_number_table.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+using PrivateToLocalTest = PassTest<::testing::Test>;
+
+TEST_F(PrivateToLocalTest, ChangeToLocal) {
+ // Change the private variable to a local, and change the types accordingly.
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+; CHECK: [[float:%[a-zA-Z_\d]+]] = OpTypeFloat 32
+ %5 = OpTypeFloat 32
+; CHECK: [[newtype:%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]]
+ %6 = OpTypePointer Private %5
+; CHECK-NOT: OpVariable [[.+]] Private
+ %8 = OpVariable %6 Private
+; CHECK: OpFunction
+ %2 = OpFunction %3 None %4
+; CHECK: OpLabel
+ %7 = OpLabel
+; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[newtype]] Function
+; CHECK: OpLoad [[float]] [[newvar]]
+ %9 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
+}
+
+TEST_F(PrivateToLocalTest, ReuseExistingType) {
+ // Change the private variable to a local, and change the types accordingly.
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+; CHECK: [[float:%[a-zA-Z_\d]+]] = OpTypeFloat 32
+ %5 = OpTypeFloat 32
+ %func_ptr = OpTypePointer Function %5
+; CHECK: [[newtype:%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]]
+; CHECK-NOT: [[%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]]
+ %6 = OpTypePointer Private %5
+; CHECK-NOT: OpVariable [[.+]] Private
+ %8 = OpVariable %6 Private
+; CHECK: OpFunction
+ %2 = OpFunction %3 None %4
+; CHECK: OpLabel
+ %7 = OpLabel
+; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[newtype]] Function
+; CHECK: OpLoad [[float]] [[newvar]]
+ %9 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
+}
+
+TEST_F(PrivateToLocalTest, UpdateAccessChain) {
+ // Change the private variable to a local, and change the AccessChain.
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+; CHECK: [[float:%[a-zA-Z_\d]+]] = OpTypeFloat
+ %float = OpTypeFloat 32
+; CHECK: [[struct:%[a-zA-Z_\d]+]] = OpTypeStruct
+ %_struct_8 = OpTypeStruct %float
+%_ptr_Private_float = OpTypePointer Private %float
+; CHECK: [[new_struct_type:%[a-zA-Z_\d]+]] = OpTypePointer Function [[struct]]
+; CHECK: [[new_float_type:%[a-zA-Z_\d]+]] = OpTypePointer Function [[float]]
+%_ptr_Private__struct_8 = OpTypePointer Private %_struct_8
+; CHECK-NOT: OpVariable [[.+]] Private
+ %11 = OpVariable %_ptr_Private__struct_8 Private
+; CHECK: OpFunction
+ %2 = OpFunction %void None %6
+; CHECK: OpLabel
+ %12 = OpLabel
+; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[new_struct_type]] Function
+; CHECK: [[member:%[a-zA-Z_\d]+]] = OpAccessChain [[new_float_type]] [[newvar]]
+ %13 = OpAccessChain %_ptr_Private_float %11 %uint_0
+; CHECK: OpLoad [[float]] [[member]]
+ %14 = OpLoad %float %13
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
+}
+
+TEST_F(PrivateToLocalTest, UseTexelPointer) {
+ // Change the private variable to a local, and change the OpImageTexelPointer.
+ const std::string text = R"(
+OpCapability SampledBuffer
+ OpCapability StorageImageExtendedFormats
+ OpCapability ImageBuffer
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %2 "min" %gl_GlobalInvocationID
+ OpExecutionMode %2 LocalSize 64 1 1
+ OpSource HLSL 600
+ OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
+ OpDecorate %4 DescriptorSet 4
+ OpDecorate %4 Binding 70
+ %uint = OpTypeInt 32 0
+ %6 = OpTypeImage %uint Buffer 0 0 0 2 R32ui
+%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6
+%_ptr_Private_6 = OpTypePointer Private %6
+ %void = OpTypeVoid
+ %10 = OpTypeFunction %void
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+ %v3uint = OpTypeVector %uint 3
+%_ptr_Input_v3uint = OpTypePointer Input %v3uint
+%_ptr_Image_uint = OpTypePointer Image %uint
+ %4 = OpVariable %_ptr_UniformConstant_6 UniformConstant
+ %16 = OpVariable %_ptr_Private_6 Private
+%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
+ %2 = OpFunction %void None %10
+ %17 = OpLabel
+; Make sure the variable was moved.
+; CHECK: OpFunction
+; CHECK-NEXT: OpLabel
+; CHECK-NEXT: OpVariable %_ptr_Function_6 Function
+ %18 = OpLoad %6 %4
+ OpStore %16 %18
+ %19 = OpImageTexelPointer %_ptr_Image_uint %16 %uint_0 %uint_0
+ %20 = OpAtomicIAdd %uint %19 %uint_1 %uint_0 %uint_1
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
+}
+
+TEST_F(PrivateToLocalTest, UsedInTwoFunctions) {
+ // Should not change because it is used in multiple functions.
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Private %5
+ %8 = OpVariable %6 Private
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %9 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %3 None %4
+ %11 = OpLabel
+ %12 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(PrivateToLocalTest, UsedInFunctionCall) {
+ // Should not change because it is used in a function call. Changing the
+ // signature of the function would require cloning the function, which is not
+ // worth it.
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+ %7 = OpTypeFunction %void %_ptr_Private_float
+ %8 = OpVariable %_ptr_Private_float Private
+ %2 = OpFunction %void None %4
+ %9 = OpLabel
+ %10 = OpFunctionCall %void %11 %8
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %void None %7
+ %12 = OpFunctionParameter %_ptr_Private_float
+ %13 = OpLabel
+ %14 = OpLoad %float %12
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(PrivateToLocalTest, CreatePointerToAmbiguousStruct1) {
+ // Test that the correct pointer type is picked up.
+ const std::string text = R"(
+; CHECK: [[struct1:%[a-zA-Z_\d]+]] = OpTypeStruct
+; CHECK: [[struct2:%[a-zA-Z_\d]+]] = OpTypeStruct
+; CHECK: [[priv_ptr:%[\w]+]] = OpTypePointer Private [[struct1]]
+; CHECK: [[fuct_ptr2:%[\w]+]] = OpTypePointer Function [[struct2]]
+; CHECK: [[fuct_ptr1:%[\w]+]] = OpTypePointer Function [[struct1]]
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[fuct_ptr1]] Function
+; CHECK: OpLoad [[struct1]] [[newvar]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %struct1 = OpTypeStruct %5
+ %struct2 = OpTypeStruct %5
+ %6 = OpTypePointer Private %struct1
+ %func_ptr2 = OpTypePointer Function %struct2
+ %8 = OpVariable %6 Private
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %9 = OpLoad %struct1 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
+}
+
+TEST_F(PrivateToLocalTest, CreatePointerToAmbiguousStruct2) {
+ // Test that the correct pointer type is picked up.
+ const std::string text = R"(
+; CHECK: [[struct1:%[a-zA-Z_\d]+]] = OpTypeStruct
+; CHECK: [[struct2:%[a-zA-Z_\d]+]] = OpTypeStruct
+; CHECK: [[priv_ptr:%[\w]+]] = OpTypePointer Private [[struct2]]
+; CHECK: [[fuct_ptr1:%[\w]+]] = OpTypePointer Function [[struct1]]
+; CHECK: [[fuct_ptr2:%[\w]+]] = OpTypePointer Function [[struct2]]
+; CHECK: OpFunction
+; CHECK: OpLabel
+; CHECK-NEXT: [[newvar:%[a-zA-Z_\d]+]] = OpVariable [[fuct_ptr2]] Function
+; CHECK: OpLoad [[struct2]] [[newvar]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %struct1 = OpTypeStruct %5
+ %struct2 = OpTypeStruct %5
+ %6 = OpTypePointer Private %struct2
+ %func_ptr2 = OpTypePointer Function %struct1
+ %8 = OpVariable %6 Private
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %9 = OpLoad %struct2 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/process_lines_test.cpp b/test/opt/process_lines_test.cpp
new file mode 100644
index 0000000..33ad4be
--- /dev/null
+++ b/test/opt/process_lines_test.cpp
@@ -0,0 +1,695 @@
+// 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 <memory>
+#include <string>
+#include <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ProcessLinesTest = PassTest<::testing::Test>;
+
+TEST_F(ProcessLinesTest, SimplePropagation) {
+ // Texture2D g_tColor[128];
+ //
+ // layout(push_constant) cbuffer PerViewConstantBuffer_t
+ // {
+ // uint g_nDataIdx;
+ // uint g_nDataIdx2;
+ // bool g_B;
+ // };
+ //
+ // SamplerState g_sAniso;
+ //
+ // struct PS_INPUT
+ // {
+ // float2 vTextureCoords : TEXCOORD2;
+ // };
+ //
+ // struct PS_OUTPUT
+ // {
+ // float4 vColor : SV_Target0;
+ // };
+ //
+ // PS_OUTPUT MainPs(PS_INPUT i)
+ // {
+ // PS_OUTPUT ps_output;
+ //
+ // uint u;
+ // if (g_B)
+ // u = g_nDataIdx;
+ // else
+ // u = g_nDataIdx2;
+ // ps_output.vColor = g_tColor[u].Sample(g_sAniso, i.vTextureCoords.xy);
+ // return ps_output;
+ // }
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+%5 = OpString "foo.frag"
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpMemberName %PerViewConstantBuffer_t 1 "g_nDataIdx2"
+OpMemberName %PerViewConstantBuffer_t 2 "g_B"
+OpName %_ ""
+OpName %u "u"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpMemberDecorate %PerViewConstantBuffer_t 2 Offset 8
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+)";
+
+ const std::string before =
+ R"(%void = OpTypeVoid
+%19 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%uint = OpTypeInt 32 0
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%int = OpTypeInt 32 1
+%int_2 = OpConstant %int 2
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_uint = OpTypePointer Function %uint
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint_128 = OpConstant %uint 128
+%_arr_36_uint_128 = OpTypeArray %36 %uint_128
+%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+%41 = OpTypeSampler
+%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
+%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
+%43 = OpTypeSampledImage %36
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%MainPs = OpFunction %void None %19
+%48 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 23 0
+%49 = OpLoad %v2float %i_vTextureCoords
+%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %50 %49
+%51 = OpLoad %PS_INPUT %i_0
+OpStore %param %51
+%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%53 = OpCompositeExtract %v4float %52 0
+OpStore %_entryPointOutput_vColor %53
+OpReturn
+OpFunctionEnd
+%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%54 = OpLabel
+%u = OpVariable %_ptr_Function_uint Function
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 27 0
+%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
+%56 = OpLoad %uint %55
+%57 = OpINotEqual %bool %56 %uint_0
+OpSelectionMerge %58 None
+OpBranchConditional %57 %59 %60
+%59 = OpLabel
+OpLine %5 28 0
+%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%62 = OpLoad %uint %61
+OpStore %u %62
+OpBranch %58
+%60 = OpLabel
+OpLine %5 30 0
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%64 = OpLoad %uint %63
+OpStore %u %64
+OpBranch %58
+%58 = OpLabel
+OpLine %5 31 0
+%65 = OpLoad %uint %u
+%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
+%67 = OpLoad %36 %66
+%68 = OpLoad %41 %g_sAniso
+%69 = OpSampledImage %43 %67 %68
+%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%71 = OpLoad %v2float %70
+%72 = OpImageSampleImplicitLod %v4float %69 %71
+%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %73 %72
+OpLine %5 32 0
+%74 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %74
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(OpNoLine
+%void = OpTypeVoid
+OpNoLine
+%19 = OpTypeFunction %void
+OpNoLine
+%float = OpTypeFloat 32
+OpNoLine
+%v2float = OpTypeVector %float 2
+OpNoLine
+%PS_INPUT = OpTypeStruct %v2float
+OpNoLine
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+OpNoLine
+%v4float = OpTypeVector %float 4
+OpNoLine
+%PS_OUTPUT = OpTypeStruct %v4float
+OpNoLine
+%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+OpNoLine
+%uint = OpTypeInt 32 0
+OpNoLine
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
+OpNoLine
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+OpNoLine
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+OpNoLine
+%int = OpTypeInt 32 1
+OpNoLine
+%int_2 = OpConstant %int 2
+OpNoLine
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+OpNoLine
+%bool = OpTypeBool
+OpNoLine
+%uint_0 = OpConstant %uint 0
+OpNoLine
+%_ptr_Function_uint = OpTypePointer Function %uint
+OpNoLine
+%int_0 = OpConstant %int 0
+OpNoLine
+%int_1 = OpConstant %int 1
+OpNoLine
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+OpNoLine
+%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
+OpNoLine
+%uint_128 = OpConstant %uint 128
+OpNoLine
+%_arr_36_uint_128 = OpTypeArray %36 %uint_128
+OpNoLine
+%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
+OpNoLine
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
+OpNoLine
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+OpNoLine
+%41 = OpTypeSampler
+OpNoLine
+%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
+OpNoLine
+%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
+OpNoLine
+%43 = OpTypeSampledImage %36
+OpNoLine
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+OpNoLine
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+OpNoLine
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+OpNoLine
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+OpNoLine
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+OpNoLine
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+OpNoLine
+%MainPs = OpFunction %void None %19
+OpNoLine
+%48 = OpLabel
+OpNoLine
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+OpNoLine
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 23 0
+%49 = OpLoad %v2float %i_vTextureCoords
+OpLine %5 23 0
+%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpLine %5 23 0
+OpStore %50 %49
+OpLine %5 23 0
+%51 = OpLoad %PS_INPUT %i_0
+OpLine %5 23 0
+OpStore %param %51
+OpLine %5 23 0
+%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+OpLine %5 23 0
+%53 = OpCompositeExtract %v4float %52 0
+OpLine %5 23 0
+OpStore %_entryPointOutput_vColor %53
+OpLine %5 23 0
+OpReturn
+OpNoLine
+OpFunctionEnd
+OpNoLine
+%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
+OpNoLine
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+OpNoLine
+%54 = OpLabel
+OpNoLine
+%u = OpVariable %_ptr_Function_uint Function
+OpNoLine
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 27 0
+%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
+OpLine %5 27 0
+%56 = OpLoad %uint %55
+OpLine %5 27 0
+%57 = OpINotEqual %bool %56 %uint_0
+OpLine %5 27 0
+OpSelectionMerge %58 None
+OpBranchConditional %57 %59 %60
+OpNoLine
+%59 = OpLabel
+OpLine %5 28 0
+%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+OpLine %5 28 0
+%62 = OpLoad %uint %61
+OpLine %5 28 0
+OpStore %u %62
+OpLine %5 28 0
+OpBranch %58
+OpNoLine
+%60 = OpLabel
+OpLine %5 30 0
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+OpLine %5 30 0
+%64 = OpLoad %uint %63
+OpLine %5 30 0
+OpStore %u %64
+OpLine %5 30 0
+OpBranch %58
+OpNoLine
+%58 = OpLabel
+OpLine %5 31 0
+%65 = OpLoad %uint %u
+OpLine %5 31 0
+%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
+OpLine %5 31 0
+%67 = OpLoad %36 %66
+OpLine %5 31 0
+%68 = OpLoad %41 %g_sAniso
+OpLine %5 31 0
+%69 = OpSampledImage %43 %67 %68
+OpLine %5 31 0
+%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
+OpLine %5 31 0
+%71 = OpLoad %v2float %70
+OpLine %5 31 0
+%72 = OpImageSampleImplicitLod %v4float %69 %71
+OpLine %5 31 0
+%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpLine %5 31 0
+OpStore %73 %72
+OpLine %5 32 0
+%74 = OpLoad %PS_OUTPUT %ps_output
+OpLine %5 32 0
+OpReturnValue %74
+OpNoLine
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<ProcessLinesPass>(predefs + before, predefs + after,
+ false, true, kLinesPropagateLines);
+}
+
+TEST_F(ProcessLinesTest, SimpleElimination) {
+ // Previous test with before and after reversed
+
+ const std::string predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
+OpExecutionMode %MainPs OriginUpperLeft
+%5 = OpString "foo.frag"
+OpSource HLSL 500
+OpName %MainPs "MainPs"
+OpName %PS_INPUT "PS_INPUT"
+OpMemberName %PS_INPUT 0 "vTextureCoords"
+OpName %PS_OUTPUT "PS_OUTPUT"
+OpMemberName %PS_OUTPUT 0 "vColor"
+OpName %_MainPs_struct_PS_INPUT_vf21_ "@MainPs(struct-PS_INPUT-vf21;"
+OpName %i "i"
+OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
+OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
+OpMemberName %PerViewConstantBuffer_t 1 "g_nDataIdx2"
+OpMemberName %PerViewConstantBuffer_t 2 "g_B"
+OpName %_ ""
+OpName %u "u"
+OpName %ps_output "ps_output"
+OpName %g_tColor "g_tColor"
+OpName %g_sAniso "g_sAniso"
+OpName %i_0 "i"
+OpName %i_vTextureCoords "i.vTextureCoords"
+OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
+OpName %param "param"
+OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
+OpMemberDecorate %PerViewConstantBuffer_t 1 Offset 4
+OpMemberDecorate %PerViewConstantBuffer_t 2 Offset 8
+OpDecorate %PerViewConstantBuffer_t Block
+OpDecorate %g_tColor DescriptorSet 0
+OpDecorate %g_sAniso DescriptorSet 0
+OpDecorate %i_vTextureCoords Location 0
+OpDecorate %_entryPointOutput_vColor Location 0
+)";
+
+ const std::string before =
+ R"(OpNoLine
+%void = OpTypeVoid
+OpNoLine
+%19 = OpTypeFunction %void
+OpNoLine
+%float = OpTypeFloat 32
+OpNoLine
+%v2float = OpTypeVector %float 2
+OpNoLine
+%PS_INPUT = OpTypeStruct %v2float
+OpNoLine
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+OpNoLine
+%v4float = OpTypeVector %float 4
+OpNoLine
+%PS_OUTPUT = OpTypeStruct %v4float
+OpNoLine
+%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+OpNoLine
+%uint = OpTypeInt 32 0
+OpNoLine
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
+OpNoLine
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+OpNoLine
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+OpNoLine
+%int = OpTypeInt 32 1
+OpNoLine
+%int_2 = OpConstant %int 2
+OpNoLine
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+OpNoLine
+%bool = OpTypeBool
+OpNoLine
+%uint_0 = OpConstant %uint 0
+OpNoLine
+%_ptr_Function_uint = OpTypePointer Function %uint
+OpNoLine
+%int_0 = OpConstant %int 0
+OpNoLine
+%int_1 = OpConstant %int 1
+OpNoLine
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+OpNoLine
+%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
+OpNoLine
+%uint_128 = OpConstant %uint 128
+OpNoLine
+%_arr_36_uint_128 = OpTypeArray %36 %uint_128
+OpNoLine
+%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
+OpNoLine
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
+OpNoLine
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+OpNoLine
+%41 = OpTypeSampler
+OpNoLine
+%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
+OpNoLine
+%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
+OpNoLine
+%43 = OpTypeSampledImage %36
+OpNoLine
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+OpNoLine
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+OpNoLine
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+OpNoLine
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+OpNoLine
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+OpNoLine
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+OpNoLine
+%MainPs = OpFunction %void None %19
+OpNoLine
+%48 = OpLabel
+OpNoLine
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+OpNoLine
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 23 0
+%49 = OpLoad %v2float %i_vTextureCoords
+OpLine %5 23 0
+%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpLine %5 23 0
+OpStore %50 %49
+OpLine %5 23 0
+%51 = OpLoad %PS_INPUT %i_0
+OpLine %5 23 0
+OpStore %param %51
+OpLine %5 23 0
+%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+OpLine %5 23 0
+%53 = OpCompositeExtract %v4float %52 0
+OpLine %5 23 0
+OpStore %_entryPointOutput_vColor %53
+OpLine %5 23 0
+OpReturn
+OpNoLine
+OpFunctionEnd
+OpNoLine
+%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
+OpNoLine
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+OpNoLine
+%54 = OpLabel
+OpNoLine
+%u = OpVariable %_ptr_Function_uint Function
+OpNoLine
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 27 0
+%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
+OpLine %5 27 0
+%56 = OpLoad %uint %55
+OpLine %5 27 0
+%57 = OpINotEqual %bool %56 %uint_0
+OpLine %5 27 0
+OpSelectionMerge %58 None
+OpBranchConditional %57 %59 %60
+OpNoLine
+%59 = OpLabel
+OpLine %5 28 0
+%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+OpLine %5 28 0
+%62 = OpLoad %uint %61
+OpLine %5 28 0
+OpStore %u %62
+OpLine %5 28 0
+OpBranch %58
+OpNoLine
+%60 = OpLabel
+OpLine %5 30 0
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+OpLine %5 30 0
+%64 = OpLoad %uint %63
+OpLine %5 30 0
+OpStore %u %64
+OpLine %5 30 0
+OpBranch %58
+OpNoLine
+%58 = OpLabel
+OpLine %5 31 0
+%65 = OpLoad %uint %u
+OpLine %5 31 0
+%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
+OpLine %5 31 0
+%67 = OpLoad %36 %66
+OpLine %5 31 0
+%68 = OpLoad %41 %g_sAniso
+OpLine %5 31 0
+%69 = OpSampledImage %43 %67 %68
+OpLine %5 31 0
+%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
+OpLine %5 31 0
+%71 = OpLoad %v2float %70
+OpLine %5 31 0
+%72 = OpImageSampleImplicitLod %v4float %69 %71
+OpLine %5 31 0
+%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpLine %5 31 0
+OpStore %73 %72
+OpLine %5 32 0
+%74 = OpLoad %PS_OUTPUT %ps_output
+OpLine %5 32 0
+OpReturnValue %74
+OpNoLine
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%void = OpTypeVoid
+%19 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%PS_INPUT = OpTypeStruct %v2float
+%_ptr_Function_PS_INPUT = OpTypePointer Function %PS_INPUT
+%v4float = OpTypeVector %float 4
+%PS_OUTPUT = OpTypeStruct %v4float
+%24 = OpTypeFunction %PS_OUTPUT %_ptr_Function_PS_INPUT
+%uint = OpTypeInt 32 0
+%PerViewConstantBuffer_t = OpTypeStruct %uint %uint %uint
+%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
+%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
+%int = OpTypeInt 32 1
+%int_2 = OpConstant %int 2
+%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_uint = OpTypePointer Function %uint
+%int_0 = OpConstant %int 0
+%int_1 = OpConstant %int 1
+%_ptr_Function_PS_OUTPUT = OpTypePointer Function %PS_OUTPUT
+%36 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%uint_128 = OpConstant %uint 128
+%_arr_36_uint_128 = OpTypeArray %36 %uint_128
+%_ptr_UniformConstant__arr_36_uint_128 = OpTypePointer UniformConstant %_arr_36_uint_128
+%g_tColor = OpVariable %_ptr_UniformConstant__arr_36_uint_128 UniformConstant
+%_ptr_UniformConstant_36 = OpTypePointer UniformConstant %36
+%41 = OpTypeSampler
+%_ptr_UniformConstant_41 = OpTypePointer UniformConstant %41
+%g_sAniso = OpVariable %_ptr_UniformConstant_41 UniformConstant
+%43 = OpTypeSampledImage %36
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
+%MainPs = OpFunction %void None %19
+%48 = OpLabel
+%i_0 = OpVariable %_ptr_Function_PS_INPUT Function
+%param = OpVariable %_ptr_Function_PS_INPUT Function
+OpLine %5 23 0
+%49 = OpLoad %v2float %i_vTextureCoords
+%50 = OpAccessChain %_ptr_Function_v2float %i_0 %int_0
+OpStore %50 %49
+%51 = OpLoad %PS_INPUT %i_0
+OpStore %param %51
+%52 = OpFunctionCall %PS_OUTPUT %_MainPs_struct_PS_INPUT_vf21_ %param
+%53 = OpCompositeExtract %v4float %52 0
+OpStore %_entryPointOutput_vColor %53
+OpReturn
+OpFunctionEnd
+%_MainPs_struct_PS_INPUT_vf21_ = OpFunction %PS_OUTPUT None %24
+%i = OpFunctionParameter %_ptr_Function_PS_INPUT
+%54 = OpLabel
+%u = OpVariable %_ptr_Function_uint Function
+%ps_output = OpVariable %_ptr_Function_PS_OUTPUT Function
+OpLine %5 27 0
+%55 = OpAccessChain %_ptr_PushConstant_uint %_ %int_2
+%56 = OpLoad %uint %55
+%57 = OpINotEqual %bool %56 %uint_0
+OpSelectionMerge %58 None
+OpBranchConditional %57 %59 %60
+%59 = OpLabel
+OpLine %5 28 0
+%61 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
+%62 = OpLoad %uint %61
+OpStore %u %62
+OpBranch %58
+%60 = OpLabel
+OpLine %5 30 0
+%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_1
+%64 = OpLoad %uint %63
+OpStore %u %64
+OpBranch %58
+%58 = OpLabel
+OpLine %5 31 0
+%65 = OpLoad %uint %u
+%66 = OpAccessChain %_ptr_UniformConstant_36 %g_tColor %65
+%67 = OpLoad %36 %66
+%68 = OpLoad %41 %g_sAniso
+%69 = OpSampledImage %43 %67 %68
+%70 = OpAccessChain %_ptr_Function_v2float %i %int_0
+%71 = OpLoad %v2float %70
+%72 = OpImageSampleImplicitLod %v4float %69 %71
+%73 = OpAccessChain %_ptr_Function_v4float %ps_output %int_0
+OpStore %73 %72
+OpLine %5 32 0
+%74 = OpLoad %PS_OUTPUT %ps_output
+OpReturnValue %74
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<ProcessLinesPass>(
+ predefs + before, predefs + after, false, true, kLinesEliminateDeadLines);
+}
+
+// TODO(greg-lunarg): Add tests to verify handling of these cases:
+//
+// TODO(greg-lunarg): Think about other tests :)
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/propagator_test.cpp b/test/opt/propagator_test.cpp
new file mode 100644
index 0000000..fb8e487
--- /dev/null
+++ b/test/opt/propagator_test.cpp
@@ -0,0 +1,219 @@
+// 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 <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/cfg.h"
+#include "source/opt/ir_context.h"
+#include "source/opt/pass.h"
+#include "source/opt/propagator.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+
+class PropagatorTest : public testing::Test {
+ protected:
+ virtual void TearDown() {
+ ctx_.reset(nullptr);
+ values_.clear();
+ values_vec_.clear();
+ }
+
+ void Assemble(const std::string& input) {
+ ctx_ = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input);
+ ASSERT_NE(nullptr, ctx_) << "Assembling failed for shader:\n"
+ << input << "\n";
+ }
+
+ bool Propagate(const SSAPropagator::VisitFunction& visit_fn) {
+ SSAPropagator propagator(ctx_.get(), visit_fn);
+ bool retval = false;
+ for (auto& fn : *ctx_->module()) {
+ retval |= propagator.Run(&fn);
+ }
+ return retval;
+ }
+
+ const std::vector<uint32_t>& GetValues() {
+ values_vec_.clear();
+ for (const auto& it : values_) {
+ values_vec_.push_back(it.second);
+ }
+ return values_vec_;
+ }
+
+ std::unique_ptr<IRContext> ctx_;
+ std::map<uint32_t, uint32_t> values_;
+ std::vector<uint32_t> values_vec_;
+};
+
+TEST_F(PropagatorTest, LocalPropagate) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %x "x"
+ OpName %y "y"
+ OpName %z "z"
+ OpName %outparm "outparm"
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_4 = OpConstant %int 4
+ %int_3 = OpConstant %int 3
+ %int_1 = OpConstant %int 1
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %x = OpVariable %_ptr_Function_int Function
+ %y = OpVariable %_ptr_Function_int Function
+ %z = OpVariable %_ptr_Function_int Function
+ OpStore %x %int_4
+ OpStore %y %int_3
+ OpStore %z %int_1
+ %20 = OpLoad %int %z
+ OpStore %outparm %20
+ OpReturn
+ OpFunctionEnd
+ )";
+ Assemble(spv_asm);
+
+ const auto visit_fn = [this](Instruction* instr, BasicBlock** dest_bb) {
+ *dest_bb = nullptr;
+ if (instr->opcode() == SpvOpStore) {
+ uint32_t lhs_id = instr->GetSingleWordOperand(0);
+ uint32_t rhs_id = instr->GetSingleWordOperand(1);
+ Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id);
+ if (rhs_def->opcode() == SpvOpConstant) {
+ uint32_t val = rhs_def->GetSingleWordOperand(2);
+ values_[lhs_id] = val;
+ return SSAPropagator::kInteresting;
+ }
+ }
+ return SSAPropagator::kVarying;
+ };
+
+ EXPECT_TRUE(Propagate(visit_fn));
+ EXPECT_THAT(GetValues(), UnorderedElementsAre(4, 3, 1));
+}
+
+TEST_F(PropagatorTest, PropagateThroughPhis) {
+ const std::string spv_asm = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %x %outparm
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpName %main "main"
+ OpName %x "x"
+ OpName %outparm "outparm"
+ OpDecorate %x Flat
+ OpDecorate %x Location 0
+ OpDecorate %outparm Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %bool = OpTypeBool
+%_ptr_Function_int = OpTypePointer Function %int
+ %int_4 = OpConstant %int 4
+ %int_3 = OpConstant %int 3
+ %int_1 = OpConstant %int 1
+%_ptr_Input_int = OpTypePointer Input %int
+ %x = OpVariable %_ptr_Input_int Input
+%_ptr_Output_int = OpTypePointer Output %int
+ %outparm = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %3
+ %4 = OpLabel
+ %5 = OpLoad %int %x
+ %6 = OpSGreaterThan %bool %5 %int_3
+ OpSelectionMerge %25 None
+ OpBranchConditional %6 %22 %23
+ %22 = OpLabel
+ %7 = OpLoad %int %int_4
+ OpBranch %25
+ %23 = OpLabel
+ %8 = OpLoad %int %int_4
+ OpBranch %25
+ %25 = OpLabel
+ %35 = OpPhi %int %7 %22 %8 %23
+ OpStore %outparm %35
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ Assemble(spv_asm);
+
+ Instruction* phi_instr = nullptr;
+ const auto visit_fn = [this, &phi_instr](Instruction* instr,
+ BasicBlock** dest_bb) {
+ *dest_bb = nullptr;
+ if (instr->opcode() == SpvOpLoad) {
+ uint32_t rhs_id = instr->GetSingleWordOperand(2);
+ Instruction* rhs_def = ctx_->get_def_use_mgr()->GetDef(rhs_id);
+ if (rhs_def->opcode() == SpvOpConstant) {
+ uint32_t val = rhs_def->GetSingleWordOperand(2);
+ values_[instr->result_id()] = val;
+ return SSAPropagator::kInteresting;
+ }
+ } else if (instr->opcode() == SpvOpPhi) {
+ phi_instr = instr;
+ SSAPropagator::PropStatus retval;
+ for (uint32_t i = 2; i < instr->NumOperands(); i += 2) {
+ uint32_t phi_arg_id = instr->GetSingleWordOperand(i);
+ auto it = values_.find(phi_arg_id);
+ if (it != values_.end()) {
+ EXPECT_EQ(it->second, 4u);
+ retval = SSAPropagator::kInteresting;
+ values_[instr->result_id()] = it->second;
+ } else {
+ retval = SSAPropagator::kNotInteresting;
+ break;
+ }
+ }
+ return retval;
+ }
+
+ return SSAPropagator::kVarying;
+ };
+
+ EXPECT_TRUE(Propagate(visit_fn));
+
+ // The propagator should've concluded that the Phi instruction has a constant
+ // value of 4.
+ EXPECT_NE(phi_instr, nullptr);
+ EXPECT_EQ(values_[phi_instr->result_id()], 4u);
+
+ EXPECT_THAT(GetValues(), UnorderedElementsAre(4u, 4u, 4u));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/reduce_load_size_test.cpp b/test/opt/reduce_load_size_test.cpp
new file mode 100644
index 0000000..7b850e3
--- /dev/null
+++ b/test/opt/reduce_load_size_test.cpp
@@ -0,0 +1,354 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 ReduceLoadSizeTest = PassTest<::testing::Test>;
+
+TEST_F(ReduceLoadSizeTest, cbuffer_load_extract) {
+ // Originally from the following HLSL:
+ // struct S {
+ // uint f;
+ // };
+ //
+ //
+ // cbuffer gBuffer { uint a[32]; };
+ //
+ // RWStructuredBuffer<S> gRWSBuffer;
+ //
+ // uint foo(uint p[32]) {
+ // return p[1];
+ // }
+ //
+ // [numthreads(1,1,1)]
+ // void main() {
+ // gRWSBuffer[0].f = foo(a);
+ // }
+ const std::string test =
+ R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpSource HLSL 600
+ OpName %type_gBuffer "type.gBuffer"
+ OpMemberName %type_gBuffer 0 "a"
+ OpName %gBuffer "gBuffer"
+ OpName %S "S"
+ OpMemberName %S 0 "f"
+ OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+ OpName %gRWSBuffer "gRWSBuffer"
+ OpName %main "main"
+ OpDecorate %_arr_uint_uint_32 ArrayStride 16
+ OpMemberDecorate %type_gBuffer 0 Offset 0
+ OpDecorate %type_gBuffer Block
+ OpMemberDecorate %S 0 Offset 0
+ OpDecorate %_runtimearr_S ArrayStride 4
+ OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+ OpDecorate %type_RWStructuredBuffer_S BufferBlock
+ OpDecorate %gBuffer DescriptorSet 0
+ OpDecorate %gBuffer Binding 0
+ OpDecorate %gRWSBuffer DescriptorSet 0
+ OpDecorate %gRWSBuffer Binding 1
+ %uint = OpTypeInt 32 0
+ %uint_32 = OpConstant %uint 32
+%_arr_uint_uint_32 = OpTypeArray %uint %uint_32
+%type_gBuffer = OpTypeStruct %_arr_uint_uint_32
+%_ptr_Uniform_type_gBuffer = OpTypePointer Uniform %type_gBuffer
+ %S = OpTypeStruct %uint
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+ %int = OpTypeInt 32 1
+ %void = OpTypeVoid
+ %15 = OpTypeFunction %void
+ %int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_uint_uint_32 = OpTypePointer Uniform %_arr_uint_uint_32
+ %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+ %gBuffer = OpVariable %_ptr_Uniform_type_gBuffer Uniform
+ %gRWSBuffer = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+ %main = OpFunction %void None %15
+ %20 = OpLabel
+; CHECK: [[ac1:%\w+]] = OpAccessChain {{%\w+}} %gBuffer %int_0
+; CHECK: [[ac2:%\w+]] = OpAccessChain {{%\w+}} [[ac1]] %uint_1
+; CHECK: [[ld:%\w+]] = OpLoad {{%\w+}} [[ac2]]
+; CHECK: OpStore {{%\w+}} [[ld]]
+ %21 = OpAccessChain %_ptr_Uniform__arr_uint_uint_32 %gBuffer %int_0
+ %22 = OpLoad %_arr_uint_uint_32 %21 ; Load of 32-element array.
+ %23 = OpCompositeExtract %uint %22 1
+ %24 = OpAccessChain %_ptr_Uniform_uint %gRWSBuffer %int_0 %uint_0 %int_0
+ OpStore %24 %23
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndMatch<ReduceLoadSize>(test, false);
+}
+
+TEST_F(ReduceLoadSizeTest, cbuffer_load_extract_vector) {
+ // Originally from the following HLSL:
+ // struct S {
+ // uint f;
+ // };
+ //
+ //
+ // cbuffer gBuffer { uint4 a; };
+ //
+ // RWStructuredBuffer<S> gRWSBuffer;
+ //
+ // uint foo(uint p[32]) {
+ // return p[1];
+ // }
+ //
+ // [numthreads(1,1,1)]
+ // void main() {
+ // gRWSBuffer[0].f = foo(a);
+ // }
+ const std::string test =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpSource HLSL 600
+OpName %type_gBuffer "type.gBuffer"
+OpMemberName %type_gBuffer 0 "a"
+OpName %gBuffer "gBuffer"
+OpName %S "S"
+OpMemberName %S 0 "f"
+OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+OpName %gRWSBuffer "gRWSBuffer"
+OpName %main "main"
+OpMemberDecorate %type_gBuffer 0 Offset 0
+OpDecorate %type_gBuffer Block
+OpMemberDecorate %S 0 Offset 0
+OpDecorate %_runtimearr_S ArrayStride 4
+OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+OpDecorate %type_RWStructuredBuffer_S BufferBlock
+OpDecorate %gBuffer DescriptorSet 0
+OpDecorate %gBuffer Binding 0
+OpDecorate %gRWSBuffer DescriptorSet 0
+OpDecorate %gRWSBuffer Binding 1
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%v4uint = OpTypeVector %uint 4
+%type_gBuffer = OpTypeStruct %v4uint
+%_ptr_Uniform_type_gBuffer = OpTypePointer Uniform %type_gBuffer
+%S = OpTypeStruct %uint
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%int = OpTypeInt 32 1
+%void = OpTypeVoid
+%15 = OpTypeFunction %void
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_v4uint = OpTypePointer Uniform %v4uint
+%uint_0 = OpConstant %uint 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%gBuffer = OpVariable %_ptr_Uniform_type_gBuffer Uniform
+%gRWSBuffer = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%main = OpFunction %void None %15
+%20 = OpLabel
+%21 = OpAccessChain %_ptr_Uniform_v4uint %gBuffer %int_0
+%22 = OpLoad %v4uint %21
+%23 = OpCompositeExtract %uint %22 1
+%24 = OpAccessChain %_ptr_Uniform_uint %gRWSBuffer %int_0 %uint_0 %int_0
+OpStore %24 %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
+}
+
+TEST_F(ReduceLoadSizeTest, cbuffer_load_5_extract) {
+ // All of the elements of the value loaded are used, so we should not
+ // change the load.
+ const std::string test =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpSource HLSL 600
+OpName %type_gBuffer "type.gBuffer"
+OpMemberName %type_gBuffer 0 "a"
+OpName %gBuffer "gBuffer"
+OpName %S "S"
+OpMemberName %S 0 "f"
+OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+OpName %gRWSBuffer "gRWSBuffer"
+OpName %main "main"
+OpDecorate %_arr_uint_uint_5 ArrayStride 16
+OpMemberDecorate %type_gBuffer 0 Offset 0
+OpDecorate %type_gBuffer Block
+OpMemberDecorate %S 0 Offset 0
+OpDecorate %_runtimearr_S ArrayStride 4
+OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+OpDecorate %type_RWStructuredBuffer_S BufferBlock
+OpDecorate %gBuffer DescriptorSet 0
+OpDecorate %gBuffer Binding 0
+OpDecorate %gRWSBuffer DescriptorSet 0
+OpDecorate %gRWSBuffer Binding 1
+%uint = OpTypeInt 32 0
+%uint_5 = OpConstant %uint 5
+%_arr_uint_uint_5 = OpTypeArray %uint %uint_5
+%type_gBuffer = OpTypeStruct %_arr_uint_uint_5
+%_ptr_Uniform_type_gBuffer = OpTypePointer Uniform %type_gBuffer
+%S = OpTypeStruct %uint
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%int = OpTypeInt 32 1
+%void = OpTypeVoid
+%15 = OpTypeFunction %void
+%int_0 = OpConstant %int 0
+%_ptr_Uniform__arr_uint_uint_5 = OpTypePointer Uniform %_arr_uint_uint_5
+%uint_0 = OpConstant %uint 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%gBuffer = OpVariable %_ptr_Uniform_type_gBuffer Uniform
+%gRWSBuffer = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%main = OpFunction %void None %15
+%20 = OpLabel
+%21 = OpAccessChain %_ptr_Uniform__arr_uint_uint_5 %gBuffer %int_0
+%22 = OpLoad %_arr_uint_uint_5 %21
+%23 = OpCompositeExtract %uint %22 0
+%24 = OpCompositeExtract %uint %22 1
+%25 = OpCompositeExtract %uint %22 2
+%26 = OpCompositeExtract %uint %22 3
+%27 = OpCompositeExtract %uint %22 4
+%28 = OpIAdd %uint %23 %24
+%29 = OpIAdd %uint %28 %25
+%30 = OpIAdd %uint %29 %26
+%31 = OpIAdd %uint %20 %27
+%32 = OpAccessChain %_ptr_Uniform_uint %gRWSBuffer %int_0 %uint_0 %int_0
+OpStore %32 %31
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
+}
+
+TEST_F(ReduceLoadSizeTest, cbuffer_load_fully_used) {
+ // The result of the load (%22) is used in an instruction that uses the whole
+ // load and has only 1 in operand. This trigger issue #1559.
+ const std::string test =
+ R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpSource HLSL 600
+OpName %type_gBuffer "type.gBuffer"
+OpMemberName %type_gBuffer 0 "a"
+OpName %gBuffer "gBuffer"
+OpName %S "S"
+OpMemberName %S 0 "f"
+OpName %type_RWStructuredBuffer_S "type.RWStructuredBuffer.S"
+OpName %gRWSBuffer "gRWSBuffer"
+OpName %main "main"
+OpMemberDecorate %type_gBuffer 0 Offset 0
+OpDecorate %type_gBuffer Block
+OpMemberDecorate %S 0 Offset 0
+OpDecorate %_runtimearr_S ArrayStride 4
+OpMemberDecorate %type_RWStructuredBuffer_S 0 Offset 0
+OpDecorate %type_RWStructuredBuffer_S BufferBlock
+OpDecorate %gBuffer DescriptorSet 0
+OpDecorate %gBuffer Binding 0
+OpDecorate %gRWSBuffer DescriptorSet 0
+OpDecorate %gRWSBuffer Binding 1
+%uint = OpTypeInt 32 0
+%uint_32 = OpConstant %uint 32
+%v4uint = OpTypeVector %uint 4
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%type_gBuffer = OpTypeStruct %v4uint
+%_ptr_Uniform_type_gBuffer = OpTypePointer Uniform %type_gBuffer
+%S = OpTypeStruct %uint
+%_runtimearr_S = OpTypeRuntimeArray %S
+%type_RWStructuredBuffer_S = OpTypeStruct %_runtimearr_S
+%_ptr_Uniform_type_RWStructuredBuffer_S = OpTypePointer Uniform %type_RWStructuredBuffer_S
+%int = OpTypeInt 32 1
+%void = OpTypeVoid
+%15 = OpTypeFunction %void
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_v4uint = OpTypePointer Uniform %v4uint
+%uint_0 = OpConstant %uint 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%gBuffer = OpVariable %_ptr_Uniform_type_gBuffer Uniform
+%gRWSBuffer = OpVariable %_ptr_Uniform_type_RWStructuredBuffer_S Uniform
+%main = OpFunction %void None %15
+%20 = OpLabel
+%21 = OpAccessChain %_ptr_Uniform_v4uint %gBuffer %int_0
+%22 = OpLoad %v4uint %21
+%23 = OpCompositeExtract %uint %22 1
+%24 = OpConvertUToF %v4float %22
+%25 = OpAccessChain %_ptr_Uniform_uint %gRWSBuffer %int_0 %uint_0 %int_0
+OpStore %25 %23
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ SinglePassRunAndCheck<ReduceLoadSize>(test, test, true, false);
+}
+
+TEST_F(ReduceLoadSizeTest, extract_with_no_index) {
+ const std::string test =
+ R"(
+ OpCapability ImageGatherExtended
+ OpExtension ""
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "P�Ma'" %12 %17
+ OpExecutionMode %4 OriginUpperLeft
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %_struct_7 = OpTypeStruct %float %float
+%_ptr_Input__struct_7 = OpTypePointer Input %_struct_7
+%_ptr_Output__struct_7 = OpTypePointer Output %_struct_7
+ %12 = OpVariable %_ptr_Input__struct_7 Input
+ %17 = OpVariable %_ptr_Output__struct_7 Output
+ %4 = OpFunction %void DontInline|Pure|Const %3
+ %245 = OpLabel
+ %13 = OpLoad %_struct_7 %12
+ %33 = OpCompositeExtract %_struct_7 %13
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto result = SinglePassRunAndDisassemble<ReduceLoadSize>(test, true, true);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/redundancy_elimination_test.cpp b/test/opt/redundancy_elimination_test.cpp
new file mode 100644
index 0000000..7d2abe8
--- /dev/null
+++ b/test/opt/redundancy_elimination_test.cpp
@@ -0,0 +1,277 @@
+// 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 "source/opt/build_module.h"
+#include "source/opt/value_number_table.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+using RedundancyEliminationTest = PassTest<::testing::Test>;
+
+// Test that it can get a simple case of local redundancy elimination.
+// The rest of the test check for extra functionality.
+TEST_F(RedundancyEliminationTest, RemoveRedundantLocalAdd) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+; CHECK: OpFAdd
+; CHECK-NOT: OpFAdd
+ %11 = OpFAdd %5 %9 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
+}
+
+// Remove a redundant add across basic blocks.
+TEST_F(RedundancyEliminationTest, RemoveRedundantAdd) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+ OpBranch %11
+ %11 = OpLabel
+; CHECK: OpFAdd
+; CHECK-NOT: OpFAdd
+ %12 = OpFAdd %5 %9 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
+}
+
+// Remove a redundant add going through a multiple basic blocks.
+TEST_F(RedundancyEliminationTest, RemoveRedundantAddDiamond) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %2 = OpFunction %3 None %4
+ %9 = OpLabel
+ %10 = OpVariable %6 Function
+ %11 = OpLoad %5 %10
+ %12 = OpFAdd %5 %11 %11
+; CHECK: OpFAdd
+; CHECK-NOT: OpFAdd
+ OpBranchConditional %8 %13 %14
+ %13 = OpLabel
+ OpBranch %15
+ %14 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ %16 = OpFAdd %5 %11 %11
+ OpReturn
+ OpFunctionEnd
+
+ )";
+ SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
+}
+
+// Remove a redundant add in a side node.
+TEST_F(RedundancyEliminationTest, RemoveRedundantAddInSideNode) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %2 = OpFunction %3 None %4
+ %9 = OpLabel
+ %10 = OpVariable %6 Function
+ %11 = OpLoad %5 %10
+ %12 = OpFAdd %5 %11 %11
+; CHECK: OpFAdd
+; CHECK-NOT: OpFAdd
+ OpBranchConditional %8 %13 %14
+ %13 = OpLabel
+ OpBranch %15
+ %14 = OpLabel
+ %16 = OpFAdd %5 %11 %11
+ OpBranch %15
+ %15 = OpLabel
+ OpReturn
+ OpFunctionEnd
+
+ )";
+ SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
+}
+
+// Remove a redundant add whose value is in the result of a phi node.
+TEST_F(RedundancyEliminationTest, RemoveRedundantAddWithPhi) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %2 = OpFunction %3 None %4
+ %9 = OpLabel
+ %10 = OpVariable %6 Function
+ %11 = OpLoad %5 %10
+ OpBranchConditional %8 %13 %14
+ %13 = OpLabel
+ %add1 = OpFAdd %5 %11 %11
+; CHECK: OpFAdd
+ OpBranch %15
+ %14 = OpLabel
+ %add2 = OpFAdd %5 %11 %11
+; CHECK: OpFAdd
+ OpBranch %15
+ %15 = OpLabel
+; CHECK: OpPhi
+ %phi = OpPhi %5 %add1 %13 %add2 %14
+; CHECK-NOT: OpFAdd
+ %16 = OpFAdd %5 %11 %11
+ OpReturn
+ OpFunctionEnd
+
+ )";
+ SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
+}
+
+// Keep the add because it is redundant on some paths, but not all paths.
+TEST_F(RedundancyEliminationTest, KeepPartiallyRedundantAdd) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %2 = OpFunction %3 None %4
+ %9 = OpLabel
+ %10 = OpVariable %6 Function
+ %11 = OpLoad %5 %10
+ OpBranchConditional %8 %13 %14
+ %13 = OpLabel
+ %add = OpFAdd %5 %11 %11
+ OpBranch %15
+ %14 = OpLabel
+ OpBranch %15
+ %15 = OpLabel
+ %16 = OpFAdd %5 %11 %11
+ OpReturn
+ OpFunctionEnd
+
+ )";
+ auto result = SinglePassRunAndDisassemble<RedundancyEliminationPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// Keep the add. Even if it is redundant on all paths, there is no single id
+// whose definition dominates the add and contains the same value.
+TEST_F(RedundancyEliminationTest, KeepRedundantAddWithoutPhi) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %2 = OpFunction %3 None %4
+ %9 = OpLabel
+ %10 = OpVariable %6 Function
+ %11 = OpLoad %5 %10
+ OpBranchConditional %8 %13 %14
+ %13 = OpLabel
+ %add1 = OpFAdd %5 %11 %11
+ OpBranch %15
+ %14 = OpLabel
+ %add2 = OpFAdd %5 %11 %11
+ OpBranch %15
+ %15 = OpLabel
+ %16 = OpFAdd %5 %11 %11
+ OpReturn
+ OpFunctionEnd
+
+ )";
+ auto result = SinglePassRunAndDisassemble<RedundancyEliminationPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/register_liveness.cpp b/test/opt/register_liveness.cpp
new file mode 100644
index 0000000..cb973d2
--- /dev/null
+++ b/test/opt/register_liveness.cpp
@@ -0,0 +1,1282 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/register_pressure.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using PassClassTest = PassTest<::testing::Test>;
+
+void CompareSets(const std::unordered_set<Instruction*>& computed,
+ const std::unordered_set<uint32_t>& expected) {
+ for (Instruction* insn : computed) {
+ EXPECT_TRUE(expected.count(insn->result_id()))
+ << "Unexpected instruction in live set: " << *insn;
+ }
+ EXPECT_EQ(computed.size(), expected.size());
+}
+
+/*
+Generated from the following GLSL
+
+#version 330
+in vec4 BaseColor;
+flat in int Count;
+void main()
+{
+ vec4 color = BaseColor;
+ vec4 acc;
+ if (Count == 0) {
+ acc = color;
+ }
+ else {
+ acc = color + vec4(0,1,2,0);
+ }
+ gl_FragColor = acc + color;
+}
+*/
+TEST_F(PassClassTest, LivenessWithIf) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %11 %15 %32
+ OpExecutionMode %4 OriginLowerLeft
+ OpSource GLSL 330
+ OpName %4 "main"
+ OpName %11 "BaseColor"
+ OpName %15 "Count"
+ OpName %32 "gl_FragColor"
+ OpDecorate %11 Location 0
+ OpDecorate %15 Flat
+ OpDecorate %15 Location 0
+ OpDecorate %32 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %10 = OpTypePointer Input %7
+ %11 = OpVariable %10 Input
+ %13 = OpTypeInt 32 1
+ %14 = OpTypePointer Input %13
+ %15 = OpVariable %14 Input
+ %17 = OpConstant %13 0
+ %18 = OpTypeBool
+ %26 = OpConstant %6 0
+ %27 = OpConstant %6 1
+ %28 = OpConstant %6 2
+ %29 = OpConstantComposite %7 %26 %27 %28 %26
+ %31 = OpTypePointer Output %7
+ %32 = OpVariable %31 Output
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %12 = OpLoad %7 %11
+ %16 = OpLoad %13 %15
+ %19 = OpIEqual %18 %16 %17
+ OpSelectionMerge %21 None
+ OpBranchConditional %19 %20 %24
+ %20 = OpLabel
+ OpBranch %21
+ %24 = OpLabel
+ %30 = OpFAdd %7 %12 %29
+ OpBranch %21
+ %21 = OpLabel
+ %36 = OpPhi %7 %12 %20 %30 %24
+ %35 = OpFAdd %7 %36 %12
+ OpStore %32 %35
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function* f = &*module->begin();
+ LivenessAnalysis* liveness_analysis = context->GetLivenessAnalysis();
+ const RegisterLiveness* register_liveness = liveness_analysis->Get(f);
+ {
+ SCOPED_TRACE("Block 5");
+ auto live_sets = register_liveness->Get(5);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 15, // %15 = OpVariable %14 Input
+ 32, // %32 = OpVariable %31 Output
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 12, // %12 = OpLoad %7 %11
+ 32, // %32 = OpVariable %31 Output
+ };
+ CompareSets(live_sets->live_out_, live_out);
+ }
+ {
+ SCOPED_TRACE("Block 20");
+ auto live_sets = register_liveness->Get(20);
+ std::unordered_set<uint32_t> live_inout{
+ 12, // %12 = OpLoad %7 %11
+ 32, // %32 = OpVariable %31 Output
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+ }
+ {
+ SCOPED_TRACE("Block 24");
+ auto live_sets = register_liveness->Get(24);
+ std::unordered_set<uint32_t> live_in{
+ 12, // %12 = OpLoad %7 %11
+ 32, // %32 = OpVariable %31 Output
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 12, // %12 = OpLoad %7 %11
+ 30, // %30 = OpFAdd %7 %12 %29
+ 32, // %32 = OpVariable %31 Output
+ };
+ CompareSets(live_sets->live_out_, live_out);
+ }
+ {
+ SCOPED_TRACE("Block 21");
+ auto live_sets = register_liveness->Get(21);
+ std::unordered_set<uint32_t> live_in{
+ 12, // %12 = OpLoad %7 %11
+ 32, // %32 = OpVariable %31 Output
+ 36, // %36 = OpPhi %7 %12 %20 %30 %24
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{};
+ CompareSets(live_sets->live_out_, live_out);
+ }
+}
+
+/*
+Generated from the following GLSL
+#version 330
+in vec4 bigColor;
+in vec4 BaseColor;
+in float f;
+flat in int Count;
+flat in uvec4 v4;
+void main()
+{
+ vec4 color = BaseColor;
+ for (int i = 0; i < Count; ++i)
+ color += bigColor;
+ float sum = 0.0;
+ for (int i = 0; i < 4; ++i) {
+ float acc = 0.0;
+ if (sum == 0.0) {
+ acc = v4[i];
+ }
+ else {
+ acc = BaseColor[i];
+ }
+ sum += acc + v4[i];
+ }
+ vec4 tv4;
+ for (int i = 0; i < 4; ++i)
+ tv4[i] = v4[i] * 4u;
+ color += vec4(sum) + tv4;
+ vec4 r;
+ r.xyz = BaseColor.xyz;
+ for (int i = 0; i < Count; ++i)
+ r.w = f;
+ color.xyz += r.xyz;
+ for (int i = 0; i < 16; i += 4)
+ for (int j = 0; j < 4; j++)
+ color *= f;
+ gl_FragColor = color + tv4;
+}
+*/
+TEST_F(PassClassTest, RegisterLiveness) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %11 %24 %28 %55 %124 %176
+ OpExecutionMode %4 OriginLowerLeft
+ OpSource GLSL 330
+ OpName %4 "main"
+ OpName %11 "BaseColor"
+ OpName %24 "Count"
+ OpName %28 "bigColor"
+ OpName %55 "v4"
+ OpName %84 "tv4"
+ OpName %124 "f"
+ OpName %176 "gl_FragColor"
+ OpDecorate %11 Location 0
+ OpDecorate %24 Flat
+ OpDecorate %24 Location 0
+ OpDecorate %28 Location 0
+ OpDecorate %55 Flat
+ OpDecorate %55 Location 0
+ OpDecorate %124 Location 0
+ OpDecorate %176 Location 0
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypeVector %6 4
+ %8 = OpTypePointer Function %7
+ %10 = OpTypePointer Input %7
+ %11 = OpVariable %10 Input
+ %13 = OpTypeInt 32 1
+ %16 = OpConstant %13 0
+ %23 = OpTypePointer Input %13
+ %24 = OpVariable %23 Input
+ %26 = OpTypeBool
+ %28 = OpVariable %10 Input
+ %33 = OpConstant %13 1
+ %35 = OpTypePointer Function %6
+ %37 = OpConstant %6 0
+ %45 = OpConstant %13 4
+ %52 = OpTypeInt 32 0
+ %53 = OpTypeVector %52 4
+ %54 = OpTypePointer Input %53
+ %55 = OpVariable %54 Input
+ %57 = OpTypePointer Input %52
+ %63 = OpTypePointer Input %6
+ %89 = OpConstant %52 4
+ %102 = OpTypeVector %6 3
+ %124 = OpVariable %63 Input
+ %158 = OpConstant %13 16
+ %175 = OpTypePointer Output %7
+ %176 = OpVariable %175 Output
+ %195 = OpUndef %7
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %84 = OpVariable %8 Function
+ %12 = OpLoad %7 %11
+ OpBranch %17
+ %17 = OpLabel
+ %191 = OpPhi %7 %12 %5 %31 %18
+ %184 = OpPhi %13 %16 %5 %34 %18
+ %25 = OpLoad %13 %24
+ %27 = OpSLessThan %26 %184 %25
+ OpLoopMerge %19 %18 None
+ OpBranchConditional %27 %18 %19
+ %18 = OpLabel
+ %29 = OpLoad %7 %28
+ %31 = OpFAdd %7 %191 %29
+ %34 = OpIAdd %13 %184 %33
+ OpBranch %17
+ %19 = OpLabel
+ OpBranch %39
+ %39 = OpLabel
+ %188 = OpPhi %6 %37 %19 %73 %51
+ %185 = OpPhi %13 %16 %19 %75 %51
+ %46 = OpSLessThan %26 %185 %45
+ OpLoopMerge %41 %51 None
+ OpBranchConditional %46 %40 %41
+ %40 = OpLabel
+ %49 = OpFOrdEqual %26 %188 %37
+ OpSelectionMerge %51 None
+ OpBranchConditional %49 %50 %61
+ %50 = OpLabel
+ %58 = OpAccessChain %57 %55 %185
+ %59 = OpLoad %52 %58
+ %60 = OpConvertUToF %6 %59
+ OpBranch %51
+ %61 = OpLabel
+ %64 = OpAccessChain %63 %11 %185
+ %65 = OpLoad %6 %64
+ OpBranch %51
+ %51 = OpLabel
+ %210 = OpPhi %6 %60 %50 %65 %61
+ %68 = OpAccessChain %57 %55 %185
+ %69 = OpLoad %52 %68
+ %70 = OpConvertUToF %6 %69
+ %71 = OpFAdd %6 %210 %70
+ %73 = OpFAdd %6 %188 %71
+ %75 = OpIAdd %13 %185 %33
+ OpBranch %39
+ %41 = OpLabel
+ OpBranch %77
+ %77 = OpLabel
+ %186 = OpPhi %13 %16 %41 %94 %78
+ %83 = OpSLessThan %26 %186 %45
+ OpLoopMerge %79 %78 None
+ OpBranchConditional %83 %78 %79
+ %78 = OpLabel
+ %87 = OpAccessChain %57 %55 %186
+ %88 = OpLoad %52 %87
+ %90 = OpIMul %52 %88 %89
+ %91 = OpConvertUToF %6 %90
+ %92 = OpAccessChain %35 %84 %186
+ OpStore %92 %91
+ %94 = OpIAdd %13 %186 %33
+ OpBranch %77
+ %79 = OpLabel
+ %96 = OpCompositeConstruct %7 %188 %188 %188 %188
+ %97 = OpLoad %7 %84
+ %98 = OpFAdd %7 %96 %97
+ %100 = OpFAdd %7 %191 %98
+ %104 = OpVectorShuffle %102 %12 %12 0 1 2
+ %106 = OpVectorShuffle %7 %195 %104 4 5 6 3
+ OpBranch %108
+ %108 = OpLabel
+ %197 = OpPhi %7 %106 %79 %208 %133
+ %196 = OpPhi %13 %16 %79 %143 %133
+ %115 = OpSLessThan %26 %196 %25
+ OpLoopMerge %110 %133 None
+ OpBranchConditional %115 %109 %110
+ %109 = OpLabel
+ OpBranch %117
+ %117 = OpLabel
+ %209 = OpPhi %7 %197 %109 %181 %118
+ %204 = OpPhi %13 %16 %109 %129 %118
+ %123 = OpSLessThan %26 %204 %45
+ OpLoopMerge %119 %118 None
+ OpBranchConditional %123 %118 %119
+ %118 = OpLabel
+ %125 = OpLoad %6 %124
+ %181 = OpCompositeInsert %7 %125 %209 3
+ %129 = OpIAdd %13 %204 %33
+ OpBranch %117
+ %119 = OpLabel
+ OpBranch %131
+ %131 = OpLabel
+ %208 = OpPhi %7 %209 %119 %183 %132
+ %205 = OpPhi %13 %16 %119 %141 %132
+ %137 = OpSLessThan %26 %205 %45
+ OpLoopMerge %133 %132 None
+ OpBranchConditional %137 %132 %133
+ %132 = OpLabel
+ %138 = OpLoad %6 %124
+ %183 = OpCompositeInsert %7 %138 %208 3
+ %141 = OpIAdd %13 %205 %33
+ OpBranch %131
+ %133 = OpLabel
+ %143 = OpIAdd %13 %196 %33
+ OpBranch %108
+ %110 = OpLabel
+ %145 = OpVectorShuffle %102 %197 %197 0 1 2
+ %147 = OpVectorShuffle %102 %100 %100 0 1 2
+ %148 = OpFAdd %102 %147 %145
+ %150 = OpVectorShuffle %7 %100 %148 4 5 6 3
+ OpBranch %152
+ %152 = OpLabel
+ %200 = OpPhi %7 %150 %110 %203 %163
+ %199 = OpPhi %13 %16 %110 %174 %163
+ %159 = OpSLessThan %26 %199 %158
+ OpLoopMerge %154 %163 None
+ OpBranchConditional %159 %153 %154
+ %153 = OpLabel
+ OpBranch %161
+ %161 = OpLabel
+ %203 = OpPhi %7 %200 %153 %170 %162
+ %201 = OpPhi %13 %16 %153 %172 %162
+ %167 = OpSLessThan %26 %201 %45
+ OpLoopMerge %163 %162 None
+ OpBranchConditional %167 %162 %163
+ %162 = OpLabel
+ %168 = OpLoad %6 %124
+ %170 = OpVectorTimesScalar %7 %203 %168
+ %172 = OpIAdd %13 %201 %33
+ OpBranch %161
+ %163 = OpLabel
+ %174 = OpIAdd %13 %199 %45
+ OpBranch %152
+ %154 = OpLabel
+ %178 = OpLoad %7 %84
+ %179 = OpFAdd %7 %200 %178
+ OpStore %176 %179
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ Function* f = &*module->begin();
+ LivenessAnalysis* liveness_analysis = context->GetLivenessAnalysis();
+ const RegisterLiveness* register_liveness = liveness_analysis->Get(f);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+
+ {
+ SCOPED_TRACE("Block 5");
+ auto live_sets = register_liveness->Get(5);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 24, // %24 = OpVariable %23 Input
+ 28, // %28 = OpVariable %10 Input
+ 55, // %55 = OpVariable %54 Input
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 24, // %24 = OpVariable %23 Input
+ 28, // %28 = OpVariable %10 Input
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 8u);
+ }
+ {
+ SCOPED_TRACE("Block 17");
+ auto live_sets = register_liveness->Get(17);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 24, // %24 = OpVariable %23 Input
+ 28, // %28 = OpVariable %10 Input
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 184, // %184 = OpPhi %13 %16 %5 %34 %18
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 28, // %28 = OpVariable %10 Input
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 184, // %184 = OpPhi %13 %16 %5 %34 %18
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 11u);
+ }
+ {
+ SCOPED_TRACE("Block 18");
+ auto live_sets = register_liveness->Get(18);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 24, // %24 = OpVariable %23 Input
+ 28, // %28 = OpVariable %10 Input
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 184, // %184 = OpPhi %13 %16 %5 %34 %18
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 24, // %24 = OpVariable %23 Input
+ 28, // %28 = OpVariable %10 Input
+ 31, // %31 = OpFAdd %7 %191 %29
+ 34, // %34 = OpIAdd %13 %184 %33
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 12u);
+ }
+ {
+ SCOPED_TRACE("Block 19");
+ auto live_sets = register_liveness->Get(19);
+ std::unordered_set<uint32_t> live_inout{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 8u);
+ }
+ {
+ SCOPED_TRACE("Block 39");
+ auto live_sets = register_liveness->Get(39);
+ std::unordered_set<uint32_t> live_inout{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 11u);
+ }
+ {
+ SCOPED_TRACE("Block 40");
+ auto live_sets = register_liveness->Get(40);
+ std::unordered_set<uint32_t> live_inout{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 11u);
+ }
+ {
+ SCOPED_TRACE("Block 50");
+ auto live_sets = register_liveness->Get(50);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 60, // %60 = OpConvertUToF %6 %59
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 12u);
+ }
+ {
+ SCOPED_TRACE("Block 61");
+ auto live_sets = register_liveness->Get(61);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 65, // %65 = OpLoad %6 %64
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 12u);
+ }
+ {
+ SCOPED_TRACE("Block 51");
+ auto live_sets = register_liveness->Get(51);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ 210, // %210 = OpPhi %6 %60 %50 %65 %61
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 73, // %73 = OpFAdd %6 %188 %71
+ 75, // %75 = OpIAdd %13 %185 %33
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 13u);
+ }
+ {
+ SCOPED_TRACE("Block 41");
+ auto live_sets = register_liveness->Get(41);
+ std::unordered_set<uint32_t> live_inout{
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 8u);
+ }
+ {
+ SCOPED_TRACE("Block 77");
+ auto live_sets = register_liveness->Get(77);
+ std::unordered_set<uint32_t> live_inout{
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 186, // %186 = OpPhi %13 %16 %41 %94 %78
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 10u);
+ }
+ {
+ SCOPED_TRACE("Block 78");
+ auto live_sets = register_liveness->Get(78);
+ std::unordered_set<uint32_t> live_in{
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 186, // %186 = OpPhi %13 %16 %41 %94 %78
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 94, // %94 = OpIAdd %13 %186 %33
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 11u);
+ }
+ {
+ SCOPED_TRACE("Block 79");
+ auto live_sets = register_liveness->Get(79);
+ std::unordered_set<uint32_t> live_in{
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 106, // %106 = OpVectorShuffle %7 %195 %104 4 5 6 3
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 9u);
+ }
+ {
+ SCOPED_TRACE("Block 108");
+ auto live_sets = register_liveness->Get(108);
+ std::unordered_set<uint32_t> live_in{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 197, // %197 = OpPhi %7 %106 %79 %208 %133
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 197, // %197 = OpPhi %7 %106 %79 %208 %133
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 8u);
+ }
+ {
+ SCOPED_TRACE("Block 109");
+ auto live_sets = register_liveness->Get(109);
+ std::unordered_set<uint32_t> live_inout{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 197, // %197 = OpPhi %7 %106 %79 %208 %133
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 7u);
+ }
+ {
+ SCOPED_TRACE("Block 117");
+ auto live_sets = register_liveness->Get(117);
+ std::unordered_set<uint32_t> live_inout{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 204, // %204 = OpPhi %13 %16 %109 %129 %118
+ 209, // %209 = OpPhi %7 %197 %109 %181 %118
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 9u);
+ }
+ {
+ SCOPED_TRACE("Block 118");
+ auto live_sets = register_liveness->Get(118);
+ std::unordered_set<uint32_t> live_in{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 204, // %204 = OpPhi %13 %16 %109 %129 %118
+ 209, // %209 = OpPhi %7 %197 %109 %181 %118
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 129, // %129 = OpIAdd %13 %204 %33
+ 176, // %176 = OpVariable %175 Output
+ 181, // %181 = OpCompositeInsert %7 %125 %209 3
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 10u);
+ }
+ {
+ SCOPED_TRACE("Block 119");
+ auto live_sets = register_liveness->Get(119);
+ std::unordered_set<uint32_t> live_inout{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 209, // %209 = OpPhi %7 %197 %109 %181 %118
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 7u);
+ }
+ {
+ SCOPED_TRACE("Block 131");
+ auto live_sets = register_liveness->Get(131);
+ std::unordered_set<uint32_t> live_inout{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 205, // %205 = OpPhi %13 %16 %119 %141 %132
+ 208, // %208 = OpPhi %7 %209 %119 %183 %132
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 9u);
+ }
+ {
+ SCOPED_TRACE("Block 132");
+ auto live_sets = register_liveness->Get(132);
+ std::unordered_set<uint32_t> live_in{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 205, // %205 = OpPhi %13 %16 %119 %141 %132
+ 208, // %208 = OpPhi %7 %209 %119 %183 %132
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 141, // %141 = OpIAdd %13 %205 %33
+ 176, // %176 = OpVariable %175 Output
+ 183, // %183 = OpCompositeInsert %7 %138 %208 3
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 10u);
+ }
+ {
+ SCOPED_TRACE("Block 133");
+ auto live_sets = register_liveness->Get(133);
+ std::unordered_set<uint32_t> live_in{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 196, // %196 = OpPhi %13 %16 %79 %143 %133
+ 208, // %208 = OpPhi %7 %209 %119 %183 %132
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 25, // %25 = OpLoad %13 %24
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 143, // %143 = OpIAdd %13 %196 %33
+ 176, // %176 = OpVariable %175 Output
+ 208, // %208 = OpPhi %7 %209 %119 %183 %132
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 8u);
+ }
+ {
+ SCOPED_TRACE("Block 110");
+ auto live_sets = register_liveness->Get(110);
+ std::unordered_set<uint32_t> live_in{
+ 84, // %84 = OpVariable %8 Function
+ 100, // %100 = OpFAdd %7 %191 %98
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 197, // %197 = OpPhi %7 %106 %79 %208 %133
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 150, // %150 = OpVectorShuffle %7 %100 %148 4 5 6 3
+ 176, // %176 = OpVariable %175 Output
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 7u);
+ }
+ {
+ SCOPED_TRACE("Block 152");
+ auto live_sets = register_liveness->Get(152);
+ std::unordered_set<uint32_t> live_inout{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 199, // %199 = OpPhi %13 %16 %110 %174 %163
+ 200, // %200 = OpPhi %7 %150 %110 %203 %163
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 6u);
+ }
+ {
+ SCOPED_TRACE("Block 153");
+ auto live_sets = register_liveness->Get(153);
+ std::unordered_set<uint32_t> live_inout{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 199, // %199 = OpPhi %13 %16 %110 %174 %163
+ 200, // %200 = OpPhi %7 %150 %110 %203 %163
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 5u);
+ }
+ {
+ SCOPED_TRACE("Block 161");
+ auto live_sets = register_liveness->Get(161);
+ std::unordered_set<uint32_t> live_inout{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 199, // %199 = OpPhi %13 %16 %110 %174 %163
+ 201, // %201 = OpPhi %13 %16 %153 %172 %162
+ 203, // %203 = OpPhi %7 %200 %153 %170 %162
+ };
+ CompareSets(live_sets->live_in_, live_inout);
+ CompareSets(live_sets->live_out_, live_inout);
+
+ EXPECT_EQ(live_sets->used_registers_, 7u);
+ }
+ {
+ SCOPED_TRACE("Block 162");
+ auto live_sets = register_liveness->Get(162);
+ std::unordered_set<uint32_t> live_in{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 199, // %199 = OpPhi %13 %16 %110 %174 %163
+ 201, // %201 = OpPhi %13 %16 %153 %172 %162
+ 203, // %203 = OpPhi %7 %200 %153 %170 %162
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 170, // %170 = OpVectorTimesScalar %7 %203 %168
+ 172, // %172 = OpIAdd %13 %201 %33
+ 176, // %176 = OpVariable %175 Output
+ 199, // %199 = OpPhi %13 %16 %110 %174 %163
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 8u);
+ }
+ {
+ SCOPED_TRACE("Block 163");
+ auto live_sets = register_liveness->Get(163);
+ std::unordered_set<uint32_t> live_in{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 199, // %199 = OpPhi %13 %16 %110 %174 %163
+ 203, // %203 = OpPhi %7 %200 %153 %170 %162
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 174, // %174 = OpIAdd %13 %199 %45
+ 176, // %176 = OpVariable %175 Output
+ 203, // %203 = OpPhi %7 %200 %153 %170 %162
+ };
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 6u);
+ }
+ {
+ SCOPED_TRACE("Block 154");
+ auto live_sets = register_liveness->Get(154);
+ std::unordered_set<uint32_t> live_in{
+ 84, // %84 = OpVariable %8 Function
+ 176, // %176 = OpVariable %175 Output
+ 200, // %200 = OpPhi %7 %150 %110 %203 %163
+ };
+ CompareSets(live_sets->live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{};
+ CompareSets(live_sets->live_out_, live_out);
+
+ EXPECT_EQ(live_sets->used_registers_, 4u);
+ }
+
+ {
+ SCOPED_TRACE("Compute loop pressure");
+ RegisterLiveness::RegionRegisterLiveness loop_reg_pressure;
+ register_liveness->ComputeLoopRegisterPressure(*ld[39], &loop_reg_pressure);
+ // Generate(*context->cfg()->block(39), &loop_reg_pressure);
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(loop_reg_pressure.live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(loop_reg_pressure.live_out_, live_out);
+
+ EXPECT_EQ(loop_reg_pressure.used_registers_, 13u);
+ }
+
+ {
+ SCOPED_TRACE("Loop Fusion simulation");
+ RegisterLiveness::RegionRegisterLiveness simulation_resut;
+ register_liveness->SimulateFusion(*ld[17], *ld[39], &simulation_resut);
+
+ std::unordered_set<uint32_t> live_in{
+ 11, // %11 = OpVariable %10 Input
+ 12, // %12 = OpLoad %7 %11
+ 24, // %24 = OpVariable %23 Input
+ 25, // %25 = OpLoad %13 %24
+ 28, // %28 = OpVariable %10 Input
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 184, // %184 = OpPhi %13 %16 %5 %34 %18
+ 185, // %185 = OpPhi %13 %16 %19 %75 %51
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(simulation_resut.live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 12, // %12 = OpLoad %7 %11
+ 25, // %25 = OpLoad %13 %24
+ 55, // %55 = OpVariable %54 Input
+ 84, // %84 = OpVariable %8 Function
+ 124, // %124 = OpVariable %63 Input
+ 176, // %176 = OpVariable %175 Output
+ 188, // %188 = OpPhi %6 %37 %19 %73 %51
+ 191, // %191 = OpPhi %7 %12 %5 %31 %18
+ };
+ CompareSets(simulation_resut.live_out_, live_out);
+
+ EXPECT_EQ(simulation_resut.used_registers_, 17u);
+ }
+}
+
+TEST_F(PassClassTest, FissionSimulation) {
+ const std::string source = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "i"
+ OpName %4 "A"
+ OpName %5 "B"
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 10
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Function %16
+ %18 = OpTypePointer Function %13
+ %19 = OpConstant %8 1
+ %2 = OpFunction %6 None %7
+ %20 = OpLabel
+ %3 = OpVariable %9 Function
+ %4 = OpVariable %17 Function
+ %5 = OpVariable %17 Function
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpPhi %8 %10 %20 %23 %24
+ OpLoopMerge %25 %24 None
+ OpBranch %26
+ %26 = OpLabel
+ %27 = OpSLessThan %12 %22 %11
+ OpBranchConditional %27 %28 %25
+ %28 = OpLabel
+ %29 = OpAccessChain %18 %5 %22
+ %30 = OpLoad %13 %29
+ %31 = OpAccessChain %18 %4 %22
+ OpStore %31 %30
+ %32 = OpAccessChain %18 %4 %22
+ %33 = OpLoad %13 %32
+ %34 = OpAccessChain %18 %5 %22
+ OpStore %34 %33
+ OpBranch %24
+ %24 = OpLabel
+ %23 = OpIAdd %8 %22 %19
+ OpBranch %21
+ %25 = OpLabel
+ OpStore %3 %22
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, source,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << source << std::endl;
+ Function* f = &*module->begin();
+ LivenessAnalysis* liveness_analysis = context->GetLivenessAnalysis();
+ const RegisterLiveness* register_liveness = liveness_analysis->Get(f);
+ LoopDescriptor& ld = *context->GetLoopDescriptor(f);
+ analysis::DefUseManager& def_use_mgr = *context->get_def_use_mgr();
+
+ {
+ RegisterLiveness::RegionRegisterLiveness l1_sim_resut;
+ RegisterLiveness::RegionRegisterLiveness l2_sim_resut;
+ std::unordered_set<Instruction*> moved_instructions{
+ def_use_mgr.GetDef(29), def_use_mgr.GetDef(30), def_use_mgr.GetDef(31),
+ def_use_mgr.GetDef(31)->NextNode()};
+ std::unordered_set<Instruction*> copied_instructions{
+ def_use_mgr.GetDef(22), def_use_mgr.GetDef(27),
+ def_use_mgr.GetDef(27)->NextNode(), def_use_mgr.GetDef(23)};
+
+ register_liveness->SimulateFission(*ld[21], moved_instructions,
+ copied_instructions, &l1_sim_resut,
+ &l2_sim_resut);
+ {
+ SCOPED_TRACE("L1 simulation");
+ std::unordered_set<uint32_t> live_in{
+ 3, // %3 = OpVariable %9 Function
+ 4, // %4 = OpVariable %17 Function
+ 5, // %5 = OpVariable %17 Function
+ 22, // %22 = OpPhi %8 %10 %20 %23 %24
+ };
+ CompareSets(l1_sim_resut.live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 3, // %3 = OpVariable %9 Function
+ 4, // %4 = OpVariable %17 Function
+ 5, // %5 = OpVariable %17 Function
+ 22, // %22 = OpPhi %8 %10 %20 %23 %24
+ };
+ CompareSets(l1_sim_resut.live_out_, live_out);
+
+ EXPECT_EQ(l1_sim_resut.used_registers_, 6u);
+ }
+ {
+ SCOPED_TRACE("L2 simulation");
+ std::unordered_set<uint32_t> live_in{
+ 3, // %3 = OpVariable %9 Function
+ 4, // %4 = OpVariable %17 Function
+ 5, // %5 = OpVariable %17 Function
+ 22, // %22 = OpPhi %8 %10 %20 %23 %24
+ };
+ CompareSets(l2_sim_resut.live_in_, live_in);
+
+ std::unordered_set<uint32_t> live_out{
+ 3, // %3 = OpVariable %9 Function
+ 22, // %22 = OpPhi %8 %10 %20 %23 %24
+ };
+ CompareSets(l2_sim_resut.live_out_, live_out);
+
+ EXPECT_EQ(l2_sim_resut.used_registers_, 6u);
+ }
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/replace_invalid_opc_test.cpp b/test/opt/replace_invalid_opc_test.cpp
new file mode 100644
index 0000000..1be904b
--- /dev/null
+++ b/test/opt/replace_invalid_opc_test.cpp
@@ -0,0 +1,566 @@
+// 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 <cstdarg>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "pass_utils.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ReplaceInvalidOpcodeTest = PassTest<::testing::Test>;
+
+TEST_F(ReplaceInvalidOpcodeTest, ReplaceInstruction) {
+ const std::string text = R"(
+; CHECK: [[special_const:%\w+]] = OpConstant %float -6.2598534e+18
+; CHECK: [[constant:%\w+]] = OpConstantComposite %v4float [[special_const]] [[special_const]] [[special_const]] [[special_const]]
+; CHECK-NOT: OpImageSampleImplicitLod
+; CHECK: OpStore [[:%\w+]] [[constant]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_6 0 BuiltIn Position
+ OpDecorate %_struct_6 Block
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+ %12 = OpTypeSampler
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+ %14 = OpTypeSampledImage %10
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_6 = OpTypeStruct %v4float
+%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6
+ %5 = OpVariable %_ptr_Output__struct_6 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v2float %float_0 %float_0
+ %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant
+ %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant
+ %main = OpFunction %void None %8
+ %26 = OpLabel
+ %27 = OpLoad %12 %25
+ %28 = OpLoad %10 %24
+ %29 = OpSampledImage %14 %28 %27
+ %30 = OpImageSampleImplicitLod %v4float %29 %23
+ %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %31 %30
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<ReplaceInvalidOpcodePass>(text, false);
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, ReplaceInstructionInNonEntryPoint) {
+ const std::string text = R"(
+; CHECK: [[special_const:%\w+]] = OpConstant %float -6.2598534e+18
+; CHECK: [[constant:%\w+]] = OpConstantComposite %v4float [[special_const]] [[special_const]] [[special_const]] [[special_const]]
+; CHECK-NOT: OpImageSampleImplicitLod
+; CHECK: OpStore [[:%\w+]] [[constant]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_6 0 BuiltIn Position
+ OpDecorate %_struct_6 Block
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+ %12 = OpTypeSampler
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+ %14 = OpTypeSampledImage %10
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_6 = OpTypeStruct %v4float
+%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6
+ %5 = OpVariable %_ptr_Output__struct_6 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v2float %float_0 %float_0
+ %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant
+ %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant
+ %main = OpFunction %void None %8
+ %26 = OpLabel
+ %27 = OpFunctionCall %void %28
+ OpReturn
+ OpFunctionEnd
+ %28 = OpFunction %void None %8
+ %29 = OpLabel
+ %30 = OpLoad %12 %25
+ %31 = OpLoad %10 %24
+ %32 = OpSampledImage %14 %31 %30
+ %33 = OpImageSampleImplicitLod %v4float %32 %23
+ %34 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %34 %33
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<ReplaceInvalidOpcodePass>(text, false);
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, ReplaceInstructionMultipleEntryPoints) {
+ const std::string text = R"(
+; CHECK: [[special_const:%\w+]] = OpConstant %float -6.2598534e+18
+; CHECK: [[constant:%\w+]] = OpConstantComposite %v4float [[special_const]] [[special_const]] [[special_const]] [[special_const]]
+; CHECK-NOT: OpImageSampleImplicitLod
+; CHECK: OpStore [[:%\w+]] [[constant]]
+; CHECK-NOT: OpImageSampleImplicitLod
+; CHECK: OpStore [[:%\w+]] [[constant]]
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5
+ OpEntryPoint Vertex %main2 "main2" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpName %main2 "main2"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_6 0 BuiltIn Position
+ OpDecorate %_struct_6 Block
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+ %12 = OpTypeSampler
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+ %14 = OpTypeSampledImage %10
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_6 = OpTypeStruct %v4float
+%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6
+ %5 = OpVariable %_ptr_Output__struct_6 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v2float %float_0 %float_0
+ %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant
+ %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant
+ %main = OpFunction %void None %8
+ %26 = OpLabel
+ %27 = OpLoad %12 %25
+ %28 = OpLoad %10 %24
+ %29 = OpSampledImage %14 %28 %27
+ %30 = OpImageSampleImplicitLod %v4float %29 %23
+ %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %31 %30
+ OpReturn
+ OpFunctionEnd
+ %main2 = OpFunction %void None %8
+ %46 = OpLabel
+ %47 = OpLoad %12 %25
+ %48 = OpLoad %10 %24
+ %49 = OpSampledImage %14 %48 %47
+ %50 = OpImageSampleImplicitLod %v4float %49 %23
+ %51 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %51 %50
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<ReplaceInvalidOpcodePass>(text, false);
+}
+TEST_F(ReplaceInvalidOpcodeTest, DontReplaceInstruction) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_6 0 BuiltIn Position
+ OpDecorate %_struct_6 Block
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+ %12 = OpTypeSampler
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+ %14 = OpTypeSampledImage %10
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_6 = OpTypeStruct %v4float
+%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6
+ %5 = OpVariable %_ptr_Output__struct_6 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v2float %float_0 %float_0
+ %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant
+ %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant
+ %main = OpFunction %void None %8
+ %26 = OpLabel
+ %27 = OpLoad %12 %25
+ %28 = OpLoad %10 %24
+ %29 = OpSampledImage %14 %28 %27
+ %30 = OpImageSampleImplicitLod %v4float %29 %23
+ %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %31 %30
+ OpReturn
+ OpFunctionEnd)";
+
+ auto result = SinglePassRunAndDisassemble<ReplaceInvalidOpcodePass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, MultipleEntryPointsDifferentStage) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5
+ OpEntryPoint Fragment %main2 "main2" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpName %main2 "main2"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_6 0 BuiltIn Position
+ OpDecorate %_struct_6 Block
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+ %12 = OpTypeSampler
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+ %14 = OpTypeSampledImage %10
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_6 = OpTypeStruct %v4float
+%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6
+ %5 = OpVariable %_ptr_Output__struct_6 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v2float %float_0 %float_0
+ %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant
+ %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant
+ %main = OpFunction %void None %8
+ %26 = OpLabel
+ %27 = OpLoad %12 %25
+ %28 = OpLoad %10 %24
+ %29 = OpSampledImage %14 %28 %27
+ %30 = OpImageSampleImplicitLod %v4float %29 %23
+ %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %31 %30
+ OpReturn
+ OpFunctionEnd
+ %main2 = OpFunction %void None %8
+ %46 = OpLabel
+ %47 = OpLoad %12 %25
+ %48 = OpLoad %10 %24
+ %49 = OpSampledImage %14 %48 %47
+ %50 = OpImageSampleImplicitLod %v4float %49 %23
+ %51 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %51 %50
+ OpReturn
+ OpFunctionEnd)";
+
+ auto result = SinglePassRunAndDisassemble<ReplaceInvalidOpcodePass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, DontReplaceLinkage) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpCapability Linkage
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_6 0 BuiltIn Position
+ OpDecorate %_struct_6 Block
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10
+ %12 = OpTypeSampler
+%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12
+ %14 = OpTypeSampledImage %10
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_6 = OpTypeStruct %v4float
+%_ptr_Output__struct_6 = OpTypePointer Output %_struct_6
+ %5 = OpVariable %_ptr_Output__struct_6 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %23 = OpConstantComposite %v2float %float_0 %float_0
+ %24 = OpVariable %_ptr_UniformConstant_10 UniformConstant
+ %25 = OpVariable %_ptr_UniformConstant_12 UniformConstant
+ %main = OpFunction %void None %8
+ %26 = OpLabel
+ %27 = OpLoad %12 %25
+ %28 = OpLoad %10 %24
+ %29 = OpSampledImage %14 %28 %27
+ %30 = OpImageSampleImplicitLod %v4float %29 %23
+ %31 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %31 %30
+ OpReturn
+ OpFunctionEnd)";
+
+ auto result = SinglePassRunAndDisassemble<ReplaceInvalidOpcodePass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, BarrierDontReplace) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpSource GLSL 450
+ OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+ OpSourceExtension "GL_GOOGLE_include_directive"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+%uint_264 = OpConstant %uint 264
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpControlBarrier %uint_2 %uint_2 %uint_264
+ OpReturn
+ OpFunctionEnd)";
+
+ auto result = SinglePassRunAndDisassemble<ReplaceInvalidOpcodePass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, BarrierReplace) {
+ const std::string text = R"(
+; CHECK-NOT: OpControlBarrier
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpSource GLSL 450
+ OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+ OpSourceExtension "GL_GOOGLE_include_directive"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+%uint_264 = OpConstant %uint 264
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ OpControlBarrier %uint_2 %uint_2 %uint_264
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<ReplaceInvalidOpcodePass>(text, false);
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, MessageTest) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ %6 = OpString "test.hlsl"
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_7 0 BuiltIn Position
+ OpDecorate %_struct_7 Block
+ %void = OpTypeVoid
+ %9 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %11 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11
+ %13 = OpTypeSampler
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+ %15 = OpTypeSampledImage %11
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_7 = OpTypeStruct %v4float
+%_ptr_Output__struct_7 = OpTypePointer Output %_struct_7
+ %5 = OpVariable %_ptr_Output__struct_7 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %24 = OpConstantComposite %v2float %float_0 %float_0
+ %25 = OpVariable %_ptr_UniformConstant_11 UniformConstant
+ %26 = OpVariable %_ptr_UniformConstant_13 UniformConstant
+ %main = OpFunction %void None %9
+ %27 = OpLabel
+ OpLine %6 2 4
+ %28 = OpLoad %13 %26
+ %29 = OpLoad %11 %25
+ %30 = OpSampledImage %15 %29 %28
+ %31 = OpImageSampleImplicitLod %v4float %30 %24
+ %32 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %32 %31
+ OpReturn
+ OpFunctionEnd)";
+
+ std::vector<Message> messages = {
+ {SPV_MSG_WARNING, "test.hlsl", 2, 4,
+ "Removing ImageSampleImplicitLod instruction because of incompatible "
+ "execution model."}};
+ SetMessageConsumer(GetTestMessageConsumer(messages));
+ auto result = SinglePassRunAndDisassemble<ReplaceInvalidOpcodePass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+TEST_F(ReplaceInvalidOpcodeTest, MultipleMessageTest) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %3 %gl_VertexIndex %5
+ OpSource GLSL 400
+ %6 = OpString "test.hlsl"
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpDecorate %3 Location 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %_struct_7 0 BuiltIn Position
+ OpDecorate %_struct_7 Block
+ %void = OpTypeVoid
+ %9 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %11 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11
+ %13 = OpTypeSampler
+%_ptr_UniformConstant_13 = OpTypePointer UniformConstant %13
+ %15 = OpTypeSampledImage %11
+ %v4float = OpTypeVector %float 4
+ %v2float = OpTypeVector %float 2
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %int = OpTypeInt 32 1
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+ %_struct_7 = OpTypeStruct %v4float
+%_ptr_Output__struct_7 = OpTypePointer Output %_struct_7
+ %5 = OpVariable %_ptr_Output__struct_7 Output
+ %int_0 = OpConstant %int 0
+ %float_0 = OpConstant %float 0
+ %24 = OpConstantComposite %v2float %float_0 %float_0
+ %25 = OpVariable %_ptr_UniformConstant_11 UniformConstant
+ %26 = OpVariable %_ptr_UniformConstant_13 UniformConstant
+ %main = OpFunction %void None %9
+ %27 = OpLabel
+ OpLine %6 2 4
+ %28 = OpLoad %13 %26
+ %29 = OpLoad %11 %25
+ %30 = OpSampledImage %15 %29 %28
+ %31 = OpImageSampleImplicitLod %v4float %30 %24
+ OpLine %6 12 4
+ %41 = OpImageSampleProjImplicitLod %v4float %30 %24
+ %32 = OpAccessChain %_ptr_Output_v4float %5 %int_0
+ OpStore %32 %31
+ OpReturn
+ OpFunctionEnd)";
+
+ std::vector<Message> messages = {
+ {SPV_MSG_WARNING, "test.hlsl", 2, 4,
+ "Removing ImageSampleImplicitLod instruction because of incompatible "
+ "execution model."},
+ {SPV_MSG_WARNING, "test.hlsl", 12, 4,
+ "Removing ImageSampleProjImplicitLod instruction because of "
+ "incompatible "
+ "execution model."}};
+ SetMessageConsumer(GetTestMessageConsumer(messages));
+ auto result = SinglePassRunAndDisassemble<ReplaceInvalidOpcodePass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/scalar_analysis.cpp b/test/opt/scalar_analysis.cpp
new file mode 100644
index 0000000..598d8c7
--- /dev/null
+++ b/test/opt/scalar_analysis.cpp
@@ -0,0 +1,1221 @@
+// Copyright (c) 2018 Google LLC.
+//
+// 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 <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "source/opt/iterator.h"
+#include "source/opt/loop_descriptor.h"
+#include "source/opt/pass.h"
+#include "source/opt/scalar_analysis.h"
+#include "source/opt/tree_iterator.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/function_utils.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::UnorderedElementsAre;
+using ScalarAnalysisTest = PassTest<::testing::Test>;
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 410 core
+layout (location = 1) out float array[10];
+void main() {
+ for (int i = 0; i < 10; ++i) {
+ array[i] = array[i+1];
+ }
+}
+*/
+TEST_F(ScalarAnalysisTest, BasicEvolutionTest) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %24
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %24 "array"
+ OpDecorate %24 Location 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpConstant %6 0
+ %16 = OpConstant %6 10
+ %17 = OpTypeBool
+ %19 = OpTypeFloat 32
+ %20 = OpTypeInt 32 0
+ %21 = OpConstant %20 10
+ %22 = OpTypeArray %19 %21
+ %23 = OpTypePointer Output %22
+ %24 = OpVariable %23 Output
+ %27 = OpConstant %6 1
+ %29 = OpTypePointer Output %19
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ OpBranch %10
+ %10 = OpLabel
+ %35 = OpPhi %6 %9 %5 %34 %13
+ OpLoopMerge %12 %13 None
+ OpBranch %14
+ %14 = OpLabel
+ %18 = OpSLessThan %17 %35 %16
+ OpBranchConditional %18 %11 %12
+ %11 = OpLabel
+ %28 = OpIAdd %6 %35 %27
+ %30 = OpAccessChain %29 %24 %28
+ %31 = OpLoad %19 %30
+ %32 = OpAccessChain %29 %24 %35
+ OpStore %32 %31
+ OpBranch %13
+ %13 = OpLabel
+ %34 = OpIAdd %6 %35 %27
+ OpBranch %10
+ %12 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ const Instruction* store = nullptr;
+ const Instruction* load = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 11)) {
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ store = &inst;
+ }
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(load, nullptr);
+ EXPECT_NE(store, nullptr);
+
+ Instruction* access_chain =
+ context->get_def_use_mgr()->GetDef(load->GetSingleWordInOperand(0));
+
+ Instruction* child = context->get_def_use_mgr()->GetDef(
+ access_chain->GetSingleWordInOperand(1));
+ const SENode* node = analysis.AnalyzeInstruction(child);
+
+ EXPECT_NE(node, nullptr);
+
+ // Unsimplified node should have the form of ADD(REC(0,1), 1)
+ EXPECT_EQ(node->GetType(), SENode::Add);
+
+ const SENode* child_1 = node->GetChild(0);
+ EXPECT_TRUE(child_1->GetType() == SENode::Constant ||
+ child_1->GetType() == SENode::RecurrentAddExpr);
+
+ const SENode* child_2 = node->GetChild(1);
+ EXPECT_TRUE(child_2->GetType() == SENode::Constant ||
+ child_2->GetType() == SENode::RecurrentAddExpr);
+
+ SENode* simplified = analysis.SimplifyExpression(const_cast<SENode*>(node));
+ // Simplified should be in the form of REC(1,1)
+ EXPECT_EQ(simplified->GetType(), SENode::RecurrentAddExpr);
+
+ EXPECT_EQ(simplified->GetChild(0)->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified->GetChild(0)->AsSEConstantNode()->FoldToSingleValue(),
+ 1);
+
+ EXPECT_EQ(simplified->GetChild(1)->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified->GetChild(1)->AsSEConstantNode()->FoldToSingleValue(),
+ 1);
+
+ EXPECT_EQ(simplified->GetChild(0), simplified->GetChild(1));
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 410 core
+layout (location = 1) out float array[10];
+layout (location = 2) flat in int loop_invariant;
+void main() {
+ for (int i = 0; i < 10; ++i) {
+ array[i] = array[i+loop_invariant];
+ }
+}
+
+*/
+TEST_F(ScalarAnalysisTest, LoadTest) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3 %4
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "array"
+ OpName %4 "loop_invariant"
+ OpDecorate %3 Location 1
+ OpDecorate %4 Flat
+ OpDecorate %4 Location 2
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeInt 32 1
+ %8 = OpTypePointer Function %7
+ %9 = OpConstant %7 0
+ %10 = OpConstant %7 10
+ %11 = OpTypeBool
+ %12 = OpTypeFloat 32
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 10
+ %15 = OpTypeArray %12 %14
+ %16 = OpTypePointer Output %15
+ %3 = OpVariable %16 Output
+ %17 = OpTypePointer Input %7
+ %4 = OpVariable %17 Input
+ %18 = OpTypePointer Output %12
+ %19 = OpConstant %7 1
+ %2 = OpFunction %5 None %6
+ %20 = OpLabel
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpPhi %7 %9 %20 %23 %24
+ OpLoopMerge %25 %24 None
+ OpBranch %26
+ %26 = OpLabel
+ %27 = OpSLessThan %11 %22 %10
+ OpBranchConditional %27 %28 %25
+ %28 = OpLabel
+ %29 = OpLoad %7 %4
+ %30 = OpIAdd %7 %22 %29
+ %31 = OpAccessChain %18 %3 %30
+ %32 = OpLoad %12 %31
+ %33 = OpAccessChain %18 %3 %22
+ OpStore %33 %32
+ OpBranch %24
+ %24 = OpLabel
+ %23 = OpIAdd %7 %22 %19
+ OpBranch %21
+ %25 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ const Instruction* load = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 28)) {
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(load, nullptr);
+
+ Instruction* access_chain =
+ context->get_def_use_mgr()->GetDef(load->GetSingleWordInOperand(0));
+
+ Instruction* child = context->get_def_use_mgr()->GetDef(
+ access_chain->GetSingleWordInOperand(1));
+ // const SENode* node =
+ // analysis.GetNodeFromInstruction(child->unique_id());
+
+ const SENode* node = analysis.AnalyzeInstruction(child);
+
+ EXPECT_NE(node, nullptr);
+
+ // Unsimplified node should have the form of ADD(REC(0,1), X)
+ EXPECT_EQ(node->GetType(), SENode::Add);
+
+ const SENode* child_1 = node->GetChild(0);
+ EXPECT_TRUE(child_1->GetType() == SENode::ValueUnknown ||
+ child_1->GetType() == SENode::RecurrentAddExpr);
+
+ const SENode* child_2 = node->GetChild(1);
+ EXPECT_TRUE(child_2->GetType() == SENode::ValueUnknown ||
+ child_2->GetType() == SENode::RecurrentAddExpr);
+
+ SENode* simplified = analysis.SimplifyExpression(const_cast<SENode*>(node));
+ EXPECT_EQ(simplified->GetType(), SENode::RecurrentAddExpr);
+
+ const SERecurrentNode* rec = simplified->AsSERecurrentNode();
+
+ EXPECT_NE(rec->GetChild(0), rec->GetChild(1));
+
+ EXPECT_EQ(rec->GetOffset()->GetType(), SENode::ValueUnknown);
+
+ EXPECT_EQ(rec->GetCoefficient()->GetType(), SENode::Constant);
+ EXPECT_EQ(rec->GetCoefficient()->AsSEConstantNode()->FoldToSingleValue(), 1u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 410 core
+layout (location = 1) out float array[10];
+layout (location = 2) flat in int loop_invariant;
+void main() {
+ array[0] = array[loop_invariant * 2 + 4 + 5 - 24 - loop_invariant -
+loop_invariant+ 16 * 3];
+}
+
+*/
+TEST_F(ScalarAnalysisTest, SimplifySimple) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3 %4
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %3 "array"
+ OpName %4 "loop_invariant"
+ OpDecorate %3 Location 1
+ OpDecorate %4 Flat
+ OpDecorate %4 Location 2
+ %5 = OpTypeVoid
+ %6 = OpTypeFunction %5
+ %7 = OpTypeFloat 32
+ %8 = OpTypeInt 32 0
+ %9 = OpConstant %8 10
+ %10 = OpTypeArray %7 %9
+ %11 = OpTypePointer Output %10
+ %3 = OpVariable %11 Output
+ %12 = OpTypeInt 32 1
+ %13 = OpConstant %12 0
+ %14 = OpTypePointer Input %12
+ %4 = OpVariable %14 Input
+ %15 = OpConstant %12 2
+ %16 = OpConstant %12 4
+ %17 = OpConstant %12 5
+ %18 = OpConstant %12 24
+ %19 = OpConstant %12 48
+ %20 = OpTypePointer Output %7
+ %2 = OpFunction %5 None %6
+ %21 = OpLabel
+ %22 = OpLoad %12 %4
+ %23 = OpIMul %12 %22 %15
+ %24 = OpIAdd %12 %23 %16
+ %25 = OpIAdd %12 %24 %17
+ %26 = OpISub %12 %25 %18
+ %28 = OpISub %12 %26 %22
+ %30 = OpISub %12 %28 %22
+ %31 = OpIAdd %12 %30 %19
+ %32 = OpAccessChain %20 %3 %31
+ %33 = OpLoad %7 %32
+ %34 = OpAccessChain %20 %3 %13
+ OpStore %34 %33
+ OpReturn
+ OpFunctionEnd
+ )";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ const Instruction* load = nullptr;
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 21)) {
+ if (inst.opcode() == SpvOp::SpvOpLoad && inst.result_id() == 33) {
+ load = &inst;
+ }
+ }
+
+ EXPECT_NE(load, nullptr);
+
+ Instruction* access_chain =
+ context->get_def_use_mgr()->GetDef(load->GetSingleWordInOperand(0));
+
+ Instruction* child = context->get_def_use_mgr()->GetDef(
+ access_chain->GetSingleWordInOperand(1));
+
+ const SENode* node = analysis.AnalyzeInstruction(child);
+
+ // Unsimplified is a very large graph with an add at the top.
+ EXPECT_NE(node, nullptr);
+ EXPECT_EQ(node->GetType(), SENode::Add);
+
+ // Simplified node should resolve down to a constant expression as the loads
+ // will eliminate themselves.
+ SENode* simplified = analysis.SimplifyExpression(const_cast<SENode*>(node));
+
+ EXPECT_EQ(simplified->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified->AsSEConstantNode()->FoldToSingleValue(), 33u);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 410 core
+layout(location = 0) in vec4 c;
+layout (location = 1) out float array[10];
+void main() {
+ int N = int(c.x);
+ for (int i = 0; i < 10; ++i) {
+ array[i] = array[i];
+ array[i] = array[i-1];
+ array[i] = array[i+1];
+ array[i+1] = array[i+1];
+ array[i+N] = array[i+N];
+ array[i] = array[i+N];
+ }
+}
+
+*/
+TEST_F(ScalarAnalysisTest, Simplify) {
+ const std::string text = R"( OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %12 %33
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource GLSL 410
+ OpName %4 "main"
+ OpName %8 "N"
+ OpName %12 "c"
+ OpName %19 "i"
+ OpName %33 "array"
+ OpDecorate %12 Location 0
+ OpDecorate %33 Location 1
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %9 = OpTypeFloat 32
+ %10 = OpTypeVector %9 4
+ %11 = OpTypePointer Input %10
+ %12 = OpVariable %11 Input
+ %13 = OpTypeInt 32 0
+ %14 = OpConstant %13 0
+ %15 = OpTypePointer Input %9
+ %20 = OpConstant %6 0
+ %27 = OpConstant %6 10
+ %28 = OpTypeBool
+ %30 = OpConstant %13 10
+ %31 = OpTypeArray %9 %30
+ %32 = OpTypePointer Output %31
+ %33 = OpVariable %32 Output
+ %36 = OpTypePointer Output %9
+ %42 = OpConstant %6 1
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %8 = OpVariable %7 Function
+ %19 = OpVariable %7 Function
+ %16 = OpAccessChain %15 %12 %14
+ %17 = OpLoad %9 %16
+ %18 = OpConvertFToS %6 %17
+ OpStore %8 %18
+ OpStore %19 %20
+ OpBranch %21
+ %21 = OpLabel
+ %78 = OpPhi %6 %20 %5 %77 %24
+ OpLoopMerge %23 %24 None
+ OpBranch %25
+ %25 = OpLabel
+ %29 = OpSLessThan %28 %78 %27
+ OpBranchConditional %29 %22 %23
+ %22 = OpLabel
+ %37 = OpAccessChain %36 %33 %78
+ %38 = OpLoad %9 %37
+ %39 = OpAccessChain %36 %33 %78
+ OpStore %39 %38
+ %43 = OpISub %6 %78 %42
+ %44 = OpAccessChain %36 %33 %43
+ %45 = OpLoad %9 %44
+ %46 = OpAccessChain %36 %33 %78
+ OpStore %46 %45
+ %49 = OpIAdd %6 %78 %42
+ %50 = OpAccessChain %36 %33 %49
+ %51 = OpLoad %9 %50
+ %52 = OpAccessChain %36 %33 %78
+ OpStore %52 %51
+ %54 = OpIAdd %6 %78 %42
+ %56 = OpIAdd %6 %78 %42
+ %57 = OpAccessChain %36 %33 %56
+ %58 = OpLoad %9 %57
+ %59 = OpAccessChain %36 %33 %54
+ OpStore %59 %58
+ %62 = OpIAdd %6 %78 %18
+ %65 = OpIAdd %6 %78 %18
+ %66 = OpAccessChain %36 %33 %65
+ %67 = OpLoad %9 %66
+ %68 = OpAccessChain %36 %33 %62
+ OpStore %68 %67
+ %72 = OpIAdd %6 %78 %18
+ %73 = OpAccessChain %36 %33 %72
+ %74 = OpLoad %9 %73
+ %75 = OpAccessChain %36 %33 %78
+ OpStore %75 %74
+ OpBranch %24
+ %24 = OpLabel
+ %77 = OpIAdd %6 %78 %42
+ OpStore %19 %77
+ OpBranch %21
+ %23 = OpLabel
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 4);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ const Instruction* loads[6];
+ const Instruction* stores[6];
+ int load_count = 0;
+ int store_count = 0;
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 22)) {
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ loads[load_count] = &inst;
+ ++load_count;
+ }
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[store_count] = &inst;
+ ++store_count;
+ }
+ }
+
+ EXPECT_EQ(load_count, 6);
+ EXPECT_EQ(store_count, 6);
+
+ Instruction* load_access_chain;
+ Instruction* store_access_chain;
+ Instruction* load_child;
+ Instruction* store_child;
+ SENode* load_node;
+ SENode* store_node;
+ SENode* subtract_node;
+ SENode* simplified_node;
+
+ // Testing [i] - [i] == 0
+ load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[0]->GetSingleWordInOperand(0));
+ store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[0]->GetSingleWordInOperand(0));
+
+ load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ load_node = analysis.AnalyzeInstruction(load_child);
+ store_node = analysis.AnalyzeInstruction(store_child);
+
+ subtract_node = analysis.CreateSubtraction(store_node, load_node);
+ simplified_node = analysis.SimplifyExpression(subtract_node);
+ EXPECT_EQ(simplified_node->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 0u);
+
+ // Testing [i] - [i-1] == 1
+ load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[1]->GetSingleWordInOperand(0));
+ store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[1]->GetSingleWordInOperand(0));
+
+ load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ load_node = analysis.AnalyzeInstruction(load_child);
+ store_node = analysis.AnalyzeInstruction(store_child);
+
+ subtract_node = analysis.CreateSubtraction(store_node, load_node);
+ simplified_node = analysis.SimplifyExpression(subtract_node);
+
+ EXPECT_EQ(simplified_node->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 1u);
+
+ // Testing [i] - [i+1] == -1
+ load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[2]->GetSingleWordInOperand(0));
+ store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[2]->GetSingleWordInOperand(0));
+
+ load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ load_node = analysis.AnalyzeInstruction(load_child);
+ store_node = analysis.AnalyzeInstruction(store_child);
+
+ subtract_node = analysis.CreateSubtraction(store_node, load_node);
+ simplified_node = analysis.SimplifyExpression(subtract_node);
+ EXPECT_EQ(simplified_node->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), -1);
+
+ // Testing [i+1] - [i+1] == 0
+ load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[3]->GetSingleWordInOperand(0));
+ store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[3]->GetSingleWordInOperand(0));
+
+ load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ load_node = analysis.AnalyzeInstruction(load_child);
+ store_node = analysis.AnalyzeInstruction(store_child);
+
+ subtract_node = analysis.CreateSubtraction(store_node, load_node);
+ simplified_node = analysis.SimplifyExpression(subtract_node);
+ EXPECT_EQ(simplified_node->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 0u);
+
+ // Testing [i+N] - [i+N] == 0
+ load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[4]->GetSingleWordInOperand(0));
+ store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[4]->GetSingleWordInOperand(0));
+
+ load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ load_node = analysis.AnalyzeInstruction(load_child);
+ store_node = analysis.AnalyzeInstruction(store_child);
+
+ subtract_node = analysis.CreateSubtraction(store_node, load_node);
+
+ simplified_node = analysis.SimplifyExpression(subtract_node);
+ EXPECT_EQ(simplified_node->GetType(), SENode::Constant);
+ EXPECT_EQ(simplified_node->AsSEConstantNode()->FoldToSingleValue(), 0u);
+
+ // Testing [i] - [i+N] == -N
+ load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[5]->GetSingleWordInOperand(0));
+ store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[5]->GetSingleWordInOperand(0));
+
+ load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ load_node = analysis.AnalyzeInstruction(load_child);
+ store_node = analysis.AnalyzeInstruction(store_child);
+
+ subtract_node = analysis.CreateSubtraction(store_node, load_node);
+ simplified_node = analysis.SimplifyExpression(subtract_node);
+ EXPECT_EQ(simplified_node->GetType(), SENode::Negative);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 430
+layout(location = 1) out float array[10];
+layout(location = 2) flat in int loop_invariant;
+void main(void) {
+ for (int i = 0; i < 10; ++i) {
+ array[i * 2 + i * 5] = array[i * i * 2];
+ array[i * 2] = array[i * 5];
+ }
+}
+
+*/
+
+TEST_F(ScalarAnalysisTest, SimplifyMultiplyInductions) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3 %4
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %5 "i"
+ OpName %3 "array"
+ OpName %4 "loop_invariant"
+ OpDecorate %3 Location 1
+ OpDecorate %4 Flat
+ OpDecorate %4 Location 2
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 10
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Output %16
+ %3 = OpVariable %17 Output
+ %18 = OpConstant %8 2
+ %19 = OpConstant %8 5
+ %20 = OpTypePointer Output %13
+ %21 = OpConstant %8 1
+ %22 = OpTypePointer Input %8
+ %4 = OpVariable %22 Input
+ %2 = OpFunction %6 None %7
+ %23 = OpLabel
+ %5 = OpVariable %9 Function
+ OpStore %5 %10
+ OpBranch %24
+ %24 = OpLabel
+ %25 = OpPhi %8 %10 %23 %26 %27
+ OpLoopMerge %28 %27 None
+ OpBranch %29
+ %29 = OpLabel
+ %30 = OpSLessThan %12 %25 %11
+ OpBranchConditional %30 %31 %28
+ %31 = OpLabel
+ %32 = OpIMul %8 %25 %18
+ %33 = OpIMul %8 %25 %19
+ %34 = OpIAdd %8 %32 %33
+ %35 = OpIMul %8 %25 %25
+ %36 = OpIMul %8 %35 %18
+ %37 = OpAccessChain %20 %3 %36
+ %38 = OpLoad %13 %37
+ %39 = OpAccessChain %20 %3 %34
+ OpStore %39 %38
+ %40 = OpIMul %8 %25 %18
+ %41 = OpIMul %8 %25 %19
+ %42 = OpAccessChain %20 %3 %41
+ %43 = OpLoad %13 %42
+ %44 = OpAccessChain %20 %3 %40
+ OpStore %44 %43
+ OpBranch %27
+ %27 = OpLabel
+ %26 = OpIAdd %8 %25 %21
+ OpStore %5 %26
+ OpBranch %24
+ %28 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ const Instruction* loads[2] = {nullptr, nullptr};
+ const Instruction* stores[2] = {nullptr, nullptr};
+ int load_count = 0;
+ int store_count = 0;
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 31)) {
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ loads[load_count] = &inst;
+ ++load_count;
+ }
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores[store_count] = &inst;
+ ++store_count;
+ }
+ }
+
+ EXPECT_EQ(load_count, 2);
+ EXPECT_EQ(store_count, 2);
+
+ Instruction* load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[0]->GetSingleWordInOperand(0));
+ Instruction* store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[0]->GetSingleWordInOperand(0));
+
+ Instruction* load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ Instruction* store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ SENode* store_node = analysis.AnalyzeInstruction(store_child);
+
+ SENode* store_simplified = analysis.SimplifyExpression(store_node);
+
+ load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[1]->GetSingleWordInOperand(0));
+ store_access_chain =
+ context->get_def_use_mgr()->GetDef(stores[1]->GetSingleWordInOperand(0));
+ load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+ store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ SENode* second_store =
+ analysis.SimplifyExpression(analysis.AnalyzeInstruction(store_child));
+ SENode* second_load =
+ analysis.SimplifyExpression(analysis.AnalyzeInstruction(load_child));
+ SENode* combined_add = analysis.SimplifyExpression(
+ analysis.CreateAddNode(second_load, second_store));
+
+ // We're checking that the two recurrent expression have been correctly
+ // folded. In store_simplified they will have been folded as the entire
+ // expression was simplified as one. In combined_add the two expressions have
+ // been simplified one after the other which means the recurrent expressions
+ // aren't exactly the same but should still be folded as they are with respect
+ // to the same loop.
+ EXPECT_EQ(combined_add, store_simplified);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 430
+void main(void) {
+ for (int i = 0; i < 10; --i) {
+ array[i] = array[i];
+ }
+}
+
+*/
+
+TEST_F(ScalarAnalysisTest, SimplifyNegativeSteps) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3 %4
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %5 "i"
+ OpName %3 "array"
+ OpName %4 "loop_invariant"
+ OpDecorate %3 Location 1
+ OpDecorate %4 Flat
+ OpDecorate %4 Location 2
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 10
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Output %16
+ %3 = OpVariable %17 Output
+ %18 = OpTypePointer Output %13
+ %19 = OpConstant %8 1
+ %20 = OpTypePointer Input %8
+ %4 = OpVariable %20 Input
+ %2 = OpFunction %6 None %7
+ %21 = OpLabel
+ %5 = OpVariable %9 Function
+ OpStore %5 %10
+ OpBranch %22
+ %22 = OpLabel
+ %23 = OpPhi %8 %10 %21 %24 %25
+ OpLoopMerge %26 %25 None
+ OpBranch %27
+ %27 = OpLabel
+ %28 = OpSLessThan %12 %23 %11
+ OpBranchConditional %28 %29 %26
+ %29 = OpLabel
+ %30 = OpAccessChain %18 %3 %23
+ %31 = OpLoad %13 %30
+ %32 = OpAccessChain %18 %3 %23
+ OpStore %32 %31
+ OpBranch %25
+ %25 = OpLabel
+ %24 = OpISub %8 %23 %19
+ OpStore %5 %24
+ OpBranch %22
+ %26 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ const Instruction* loads[1] = {nullptr};
+ int load_count = 0;
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 29)) {
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ loads[load_count] = &inst;
+ ++load_count;
+ }
+ }
+
+ EXPECT_EQ(load_count, 1);
+
+ Instruction* load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[0]->GetSingleWordInOperand(0));
+ Instruction* load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+
+ SENode* load_node = analysis.AnalyzeInstruction(load_child);
+
+ EXPECT_TRUE(load_node);
+ EXPECT_EQ(load_node->GetType(), SENode::RecurrentAddExpr);
+ EXPECT_TRUE(load_node->AsSERecurrentNode());
+
+ SENode* child_1 = load_node->AsSERecurrentNode()->GetCoefficient();
+ SENode* child_2 = load_node->AsSERecurrentNode()->GetOffset();
+
+ EXPECT_EQ(child_1->GetType(), SENode::Constant);
+ EXPECT_EQ(child_2->GetType(), SENode::Constant);
+
+ EXPECT_EQ(child_1->AsSEConstantNode()->FoldToSingleValue(), -1);
+ EXPECT_EQ(child_2->AsSEConstantNode()->FoldToSingleValue(), 0u);
+
+ SERecurrentNode* load_simplified =
+ analysis.SimplifyExpression(load_node)->AsSERecurrentNode();
+
+ EXPECT_TRUE(load_simplified);
+ EXPECT_EQ(load_node, load_simplified);
+
+ EXPECT_EQ(load_simplified->GetType(), SENode::RecurrentAddExpr);
+ EXPECT_TRUE(load_simplified->AsSERecurrentNode());
+
+ SENode* simplified_child_1 =
+ load_simplified->AsSERecurrentNode()->GetCoefficient();
+ SENode* simplified_child_2 =
+ load_simplified->AsSERecurrentNode()->GetOffset();
+
+ EXPECT_EQ(child_1, simplified_child_1);
+ EXPECT_EQ(child_2, simplified_child_2);
+}
+
+/*
+Generated from the following GLSL + --eliminate-local-multi-store
+
+#version 430
+void main(void) {
+ for (int i = 0; i < 10; --i) {
+ array[i] = array[i];
+ }
+}
+
+*/
+
+TEST_F(ScalarAnalysisTest, SimplifyInductionsAndLoads) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3 %4
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %5 "i"
+ OpName %3 "array"
+ OpName %4 "N"
+ OpDecorate %3 Location 1
+ OpDecorate %4 Flat
+ OpDecorate %4 Location 2
+ %6 = OpTypeVoid
+ %7 = OpTypeFunction %6
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %10 = OpConstant %8 0
+ %11 = OpConstant %8 10
+ %12 = OpTypeBool
+ %13 = OpTypeFloat 32
+ %14 = OpTypeInt 32 0
+ %15 = OpConstant %14 10
+ %16 = OpTypeArray %13 %15
+ %17 = OpTypePointer Output %16
+ %3 = OpVariable %17 Output
+ %18 = OpConstant %8 2
+ %19 = OpTypePointer Input %8
+ %4 = OpVariable %19 Input
+ %20 = OpTypePointer Output %13
+ %21 = OpConstant %8 1
+ %2 = OpFunction %6 None %7
+ %22 = OpLabel
+ %5 = OpVariable %9 Function
+ OpStore %5 %10
+ OpBranch %23
+ %23 = OpLabel
+ %24 = OpPhi %8 %10 %22 %25 %26
+ OpLoopMerge %27 %26 None
+ OpBranch %28
+ %28 = OpLabel
+ %29 = OpSLessThan %12 %24 %11
+ OpBranchConditional %29 %30 %27
+ %30 = OpLabel
+ %31 = OpLoad %8 %4
+ %32 = OpIMul %8 %18 %31
+ %33 = OpIAdd %8 %24 %32
+ %35 = OpIAdd %8 %24 %31
+ %36 = OpAccessChain %20 %3 %35
+ %37 = OpLoad %13 %36
+ %38 = OpAccessChain %20 %3 %33
+ OpStore %38 %37
+ %39 = OpIMul %8 %18 %24
+ %41 = OpIMul %8 %18 %31
+ %42 = OpIAdd %8 %39 %41
+ %43 = OpIAdd %8 %42 %21
+ %44 = OpIMul %8 %18 %24
+ %46 = OpIAdd %8 %44 %31
+ %47 = OpIAdd %8 %46 %21
+ %48 = OpAccessChain %20 %3 %47
+ %49 = OpLoad %13 %48
+ %50 = OpAccessChain %20 %3 %43
+ OpStore %50 %49
+ OpBranch %26
+ %26 = OpLabel
+ %25 = OpISub %8 %24 %21
+ OpStore %5 %25
+ OpBranch %23
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ std::vector<const Instruction*> loads{};
+ std::vector<const Instruction*> stores{};
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 30)) {
+ if (inst.opcode() == SpvOp::SpvOpLoad) {
+ loads.push_back(&inst);
+ }
+ if (inst.opcode() == SpvOp::SpvOpStore) {
+ stores.push_back(&inst);
+ }
+ }
+
+ EXPECT_EQ(loads.size(), 3u);
+ EXPECT_EQ(stores.size(), 2u);
+ {
+ Instruction* store_access_chain = context->get_def_use_mgr()->GetDef(
+ stores[0]->GetSingleWordInOperand(0));
+
+ Instruction* store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+
+ SENode* store_node = analysis.AnalyzeInstruction(store_child);
+
+ SENode* store_simplified = analysis.SimplifyExpression(store_node);
+
+ Instruction* load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[1]->GetSingleWordInOperand(0));
+
+ Instruction* load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+
+ SENode* load_node = analysis.AnalyzeInstruction(load_child);
+
+ SENode* load_simplified = analysis.SimplifyExpression(load_node);
+
+ SENode* difference =
+ analysis.CreateSubtraction(store_simplified, load_simplified);
+
+ SENode* difference_simplified = analysis.SimplifyExpression(difference);
+
+ // Check that i+2*N - i*N, turns into just N when both sides have already
+ // been simplified into a single recurrent expression.
+ EXPECT_EQ(difference_simplified->GetType(), SENode::ValueUnknown);
+
+ // Check that the inverse, i*N - i+2*N turns into -N.
+ SENode* difference_inverse = analysis.SimplifyExpression(
+ analysis.CreateSubtraction(load_simplified, store_simplified));
+
+ EXPECT_EQ(difference_inverse->GetType(), SENode::Negative);
+ EXPECT_EQ(difference_inverse->GetChild(0)->GetType(), SENode::ValueUnknown);
+ EXPECT_EQ(difference_inverse->GetChild(0), difference_simplified);
+ }
+
+ {
+ Instruction* store_access_chain = context->get_def_use_mgr()->GetDef(
+ stores[1]->GetSingleWordInOperand(0));
+
+ Instruction* store_child = context->get_def_use_mgr()->GetDef(
+ store_access_chain->GetSingleWordInOperand(1));
+ SENode* store_node = analysis.AnalyzeInstruction(store_child);
+ SENode* store_simplified = analysis.SimplifyExpression(store_node);
+
+ Instruction* load_access_chain =
+ context->get_def_use_mgr()->GetDef(loads[2]->GetSingleWordInOperand(0));
+
+ Instruction* load_child = context->get_def_use_mgr()->GetDef(
+ load_access_chain->GetSingleWordInOperand(1));
+
+ SENode* load_node = analysis.AnalyzeInstruction(load_child);
+
+ SENode* load_simplified = analysis.SimplifyExpression(load_node);
+
+ SENode* difference =
+ analysis.CreateSubtraction(store_simplified, load_simplified);
+ SENode* difference_simplified = analysis.SimplifyExpression(difference);
+
+ // Check that 2*i + 2*N + 1 - 2*i + N + 1, turns into just N when both
+ // sides have already been simplified into a single recurrent expression.
+ EXPECT_EQ(difference_simplified->GetType(), SENode::ValueUnknown);
+
+ // Check that the inverse, (2*i + N + 1) - (2*i + 2*N + 1) turns into -N.
+ SENode* difference_inverse = analysis.SimplifyExpression(
+ analysis.CreateSubtraction(load_simplified, store_simplified));
+
+ EXPECT_EQ(difference_inverse->GetType(), SENode::Negative);
+ EXPECT_EQ(difference_inverse->GetChild(0)->GetType(), SENode::ValueUnknown);
+ EXPECT_EQ(difference_inverse->GetChild(0), difference_simplified);
+ }
+}
+
+/* Generated from the following GLSL + --eliminate-local-multi-store
+
+ #version 430
+ layout(location = 1) out float array[10];
+ layout(location = 2) flat in int N;
+ void main(void) {
+ int step = 0;
+ for (int i = 0; i < N; i += step) {
+ step++;
+ }
+ }
+*/
+TEST_F(ScalarAnalysisTest, InductionWithVariantStep) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main" %3 %4
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ OpName %2 "main"
+ OpName %5 "step"
+ OpName %6 "i"
+ OpName %3 "N"
+ OpName %4 "array"
+ OpDecorate %3 Flat
+ OpDecorate %3 Location 2
+ OpDecorate %4 Location 1
+ %7 = OpTypeVoid
+ %8 = OpTypeFunction %7
+ %9 = OpTypeInt 32 1
+ %10 = OpTypePointer Function %9
+ %11 = OpConstant %9 0
+ %12 = OpTypePointer Input %9
+ %3 = OpVariable %12 Input
+ %13 = OpTypeBool
+ %14 = OpConstant %9 1
+ %15 = OpTypeFloat 32
+ %16 = OpTypeInt 32 0
+ %17 = OpConstant %16 10
+ %18 = OpTypeArray %15 %17
+ %19 = OpTypePointer Output %18
+ %4 = OpVariable %19 Output
+ %2 = OpFunction %7 None %8
+ %20 = OpLabel
+ %5 = OpVariable %10 Function
+ %6 = OpVariable %10 Function
+ OpStore %5 %11
+ OpStore %6 %11
+ OpBranch %21
+ %21 = OpLabel
+ %22 = OpPhi %9 %11 %20 %23 %24
+ %25 = OpPhi %9 %11 %20 %26 %24
+ OpLoopMerge %27 %24 None
+ OpBranch %28
+ %28 = OpLabel
+ %29 = OpLoad %9 %3
+ %30 = OpSLessThan %13 %25 %29
+ OpBranchConditional %30 %31 %27
+ %31 = OpLabel
+ %23 = OpIAdd %9 %22 %14
+ OpStore %5 %23
+ OpBranch %24
+ %24 = OpLabel
+ %26 = OpIAdd %9 %25 %23
+ OpStore %6 %26
+ OpBranch %21
+ %27 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ Module* module = context->module();
+ EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
+ << text << std::endl;
+ const Function* f = spvtest::GetFunction(module, 2);
+ ScalarEvolutionAnalysis analysis{context.get()};
+
+ std::vector<const Instruction*> phis{};
+
+ for (const Instruction& inst : *spvtest::GetBasicBlock(f, 21)) {
+ if (inst.opcode() == SpvOp::SpvOpPhi) {
+ phis.push_back(&inst);
+ }
+ }
+
+ EXPECT_EQ(phis.size(), 2u);
+ SENode* phi_node_1 = analysis.AnalyzeInstruction(phis[0]);
+ SENode* phi_node_2 = analysis.AnalyzeInstruction(phis[1]);
+ phi_node_1->DumpDot(std::cout, true);
+ EXPECT_NE(phi_node_1, nullptr);
+ EXPECT_NE(phi_node_2, nullptr);
+
+ EXPECT_EQ(phi_node_1->GetType(), SENode::RecurrentAddExpr);
+ EXPECT_EQ(phi_node_2->GetType(), SENode::CanNotCompute);
+
+ SENode* simplified_1 = analysis.SimplifyExpression(phi_node_1);
+ SENode* simplified_2 = analysis.SimplifyExpression(phi_node_2);
+
+ EXPECT_EQ(simplified_1->GetType(), SENode::RecurrentAddExpr);
+ EXPECT_EQ(simplified_2->GetType(), SENode::CanNotCompute);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/scalar_replacement_test.cpp b/test/opt/scalar_replacement_test.cpp
new file mode 100644
index 0000000..a53f09d
--- /dev/null
+++ b/test/opt/scalar_replacement_test.cpp
@@ -0,0 +1,1625 @@
+// 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 "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ScalarReplacementTest = PassTest<::testing::Test>;
+
+TEST_F(ScalarReplacementTest, SimpleStruct) {
+ const std::string text = R"(
+;
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[elem:%\w+]]
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: [[elem_ptr:%\w+]] = OpTypePointer Function [[elem]]
+; CHECK: OpConstantNull [[struct]]
+; CHECK: [[null:%\w+]] = OpConstantNull [[elem]]
+; CHECK-NOT: OpVariable [[struct_ptr]]
+; CHECK: [[one:%\w+]] = OpVariable [[elem_ptr]] Function [[null]]
+; CHECK-NEXT: [[two:%\w+]] = OpVariable [[elem_ptr]] Function [[null]]
+; CHECK-NOT: OpVariable [[elem_ptr]] Function [[null]]
+; CHECK-NOT: OpVariable [[struct_ptr]]
+; CHECK-NOT: OpInBoundsAccessChain
+; CHECK: [[l1:%\w+]] = OpLoad [[elem]] [[two]]
+; CHECK-NOT: OpAccessChain
+; CHECK: [[l2:%\w+]] = OpLoad [[elem]] [[one]]
+; CHECK: OpIAdd [[elem]] [[l1]] [[l2]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %6 "simple_struct"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpTypeStruct %2 %2 %2 %2
+%4 = OpTypePointer Function %3
+%5 = OpTypePointer Function %2
+%6 = OpTypeFunction %2
+%7 = OpConstantNull %3
+%8 = OpConstant %2 0
+%9 = OpConstant %2 1
+%10 = OpConstant %2 2
+%11 = OpConstant %2 3
+%12 = OpFunction %2 None %6
+%13 = OpLabel
+%14 = OpVariable %4 Function %7
+%15 = OpInBoundsAccessChain %5 %14 %8
+%16 = OpLoad %2 %15
+%17 = OpAccessChain %5 %14 %10
+%18 = OpLoad %2 %17
+%19 = OpIAdd %2 %16 %18
+OpReturnValue %19
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, StructInitialization) {
+ const std::string text = R"(
+;
+; CHECK: [[elem:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[elem]] [[elem]] [[elem]] [[elem]]
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: [[elem_ptr:%\w+]] = OpTypePointer Function [[elem]]
+; CHECK: [[zero:%\w+]] = OpConstant [[elem]] 0
+; CHECK: [[undef:%\w+]] = OpUndef [[elem]]
+; CHECK: [[two:%\w+]] = OpConstant [[elem]] 2
+; CHECK: [[null:%\w+]] = OpConstantNull [[elem]]
+; CHECK-NOT: OpVariable [[struct_ptr]]
+; CHECK: OpVariable [[elem_ptr]] Function [[null]]
+; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[two]]
+; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]]
+; CHECK-NEXT: OpVariable [[elem_ptr]] Function
+; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[zero]]
+; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %6 "struct_init"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpTypeStruct %2 %2 %2 %2
+%4 = OpTypePointer Function %3
+%20 = OpTypePointer Function %2
+%6 = OpTypeFunction %1
+%7 = OpConstant %2 0
+%8 = OpUndef %2
+%9 = OpConstant %2 2
+%30 = OpConstant %2 1
+%31 = OpConstant %2 3
+%10 = OpConstantNull %2
+%11 = OpConstantComposite %3 %7 %8 %9 %10
+%12 = OpFunction %1 None %6
+%13 = OpLabel
+%14 = OpVariable %4 Function %11
+%15 = OpAccessChain %20 %14 %7
+OpStore %15 %10
+%16 = OpAccessChain %20 %14 %9
+OpStore %16 %10
+%17 = OpAccessChain %20 %14 %30
+OpStore %17 %10
+%18 = OpAccessChain %20 %14 %31
+OpStore %18 %10
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, SpecConstantInitialization) {
+ const std::string text = R"(
+;
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[int]] [[int]]
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: [[int_ptr:%\w+]] = OpTypePointer Function [[int]]
+; CHECK: [[spec_comp:%\w+]] = OpSpecConstantComposite [[struct]]
+; CHECK: [[ex0:%\w+]] = OpSpecConstantOp [[int]] CompositeExtract [[spec_comp]] 0
+; CHECK: [[ex1:%\w+]] = OpSpecConstantOp [[int]] CompositeExtract [[spec_comp]] 1
+; CHECK-NOT: OpVariable [[struct]]
+; CHECK: OpVariable [[int_ptr]] Function [[ex1]]
+; CHECK-NEXT: OpVariable [[int_ptr]] Function [[ex0]]
+; CHECK-NOT: OpVariable [[struct]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %6 "spec_const"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpTypeStruct %2 %2
+%4 = OpTypePointer Function %3
+%20 = OpTypePointer Function %2
+%5 = OpTypeFunction %1
+%6 = OpConstant %2 0
+%30 = OpConstant %2 1
+%7 = OpSpecConstant %2 0
+%8 = OpSpecConstantOp %2 IAdd %7 %7
+%9 = OpSpecConstantComposite %3 %7 %8
+%10 = OpFunction %1 None %5
+%11 = OpLabel
+%12 = OpVariable %4 Function %9
+%13 = OpAccessChain %20 %12 %6
+%14 = OpLoad %2 %13
+%15 = OpAccessChain %20 %12 %30
+%16 = OpLoad %2 %15
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+// TODO(alanbaker): Re-enable when vector and matrix scalarization is supported.
+// TEST_F(ScalarReplacementTest, VectorInitialization) {
+// const std::string text = R"(
+// ;
+// ; CHECK: [[elem:%\w+]] = OpTypeInt 32 0
+// ; CHECK: [[vector:%\w+]] = OpTypeVector [[elem]] 4
+// ; CHECK: [[vector_ptr:%\w+]] = OpTypePointer Function [[vector]]
+// ; CHECK: [[elem_ptr:%\w+]] = OpTypePointer Function [[elem]]
+// ; CHECK: [[zero:%\w+]] = OpConstant [[elem]] 0
+// ; CHECK: [[undef:%\w+]] = OpUndef [[elem]]
+// ; CHECK: [[two:%\w+]] = OpConstant [[elem]] 2
+// ; CHECK: [[null:%\w+]] = OpConstantNull [[elem]]
+// ; CHECK-NOT: OpVariable [[vector_ptr]]
+// ; CHECK: OpVariable [[elem_ptr]] Function [[zero]]
+// ; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]]
+// ; CHECK-NEXT: OpVariable [[elem_ptr]] Function
+// ; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[two]]
+// ; CHECK-NEXT: OpVariable [[elem_ptr]] Function [[null]]
+// ; CHECK-NOT: OpVariable [[elem_ptr]] Function [[undef]]
+// ;
+// OpCapability Shader
+// OpCapability Linkage
+// OpMemoryModel Logical GLSL450
+// OpName %6 "vector_init"
+// %1 = OpTypeVoid
+// %2 = OpTypeInt 32 0
+// %3 = OpTypeVector %2 4
+// %4 = OpTypePointer Function %3
+// %20 = OpTypePointer Function %2
+// %6 = OpTypeFunction %1
+// %7 = OpConstant %2 0
+// %8 = OpUndef %2
+// %9 = OpConstant %2 2
+// %30 = OpConstant %2 1
+// %31 = OpConstant %2 3
+// %10 = OpConstantNull %2
+// %11 = OpConstantComposite %3 %10 %9 %8 %7
+// %12 = OpFunction %1 None %6
+// %13 = OpLabel
+// %14 = OpVariable %4 Function %11
+// %15 = OpAccessChain %20 %14 %7
+// OpStore %15 %10
+// %16 = OpAccessChain %20 %14 %9
+// OpStore %16 %10
+// %17 = OpAccessChain %20 %14 %30
+// OpStore %17 %10
+// %18 = OpAccessChain %20 %14 %31
+// OpStore %18 %10
+// OpReturn
+// OpFunctionEnd
+// )";
+//
+// SinglePassRunAndMatch<opt::ScalarReplacementPass>(text, true);
+// }
+//
+// TEST_F(ScalarReplacementTest, MatrixInitialization) {
+// const std::string text = R"(
+// ;
+// ; CHECK: [[float:%\w+]] = OpTypeFloat 32
+// ; CHECK: [[vector:%\w+]] = OpTypeVector [[float]] 2
+// ; CHECK: [[matrix:%\w+]] = OpTypeMatrix [[vector]] 2
+// ; CHECK: [[matrix_ptr:%\w+]] = OpTypePointer Function [[matrix]]
+// ; CHECK: [[float_ptr:%\w+]] = OpTypePointer Function [[float]]
+// ; CHECK: [[vec_ptr:%\w+]] = OpTypePointer Function [[vector]]
+// ; CHECK: [[zerof:%\w+]] = OpConstant [[float]] 0
+// ; CHECK: [[onef:%\w+]] = OpConstant [[float]] 1
+// ; CHECK: [[one_zero:%\w+]] = OpConstantComposite [[vector]] [[onef]]
+// [[zerof]] ; CHECK: [[zero_one:%\w+]] = OpConstantComposite [[vector]]
+// [[zerof]] [[onef]] ; CHECK: [[const_mat:%\w+]] = OpConstantComposite
+// [[matrix]] [[one_zero]]
+// [[zero_one]] ; CHECK-NOT: OpVariable [[matrix]] ; CHECK-NOT: OpVariable
+// [[vector]] Function [[one_zero]] ; CHECK: [[f1:%\w+]] = OpVariable
+// [[float_ptr]] Function [[zerof]] ; CHECK-NEXT: [[f2:%\w+]] = OpVariable
+// [[float_ptr]] Function [[onef]] ; CHECK-NEXT: [[vec_var:%\w+]] = OpVariable
+// [[vec_ptr]] Function [[zero_one]] ; CHECK-NOT: OpVariable [[matrix]] ;
+// CHECK-NOT: OpVariable [[vector]] Function [[one_zero]]
+// ;
+// OpCapability Shader
+// OpCapability Linkage
+// OpMemoryModel Logical GLSL450
+// OpName %7 "matrix_init"
+// %1 = OpTypeVoid
+// %2 = OpTypeFloat 32
+// %3 = OpTypeVector %2 2
+// %4 = OpTypeMatrix %3 2
+// %5 = OpTypePointer Function %4
+// %6 = OpTypePointer Function %2
+// %30 = OpTypePointer Function %3
+// %10 = OpTypeInt 32 0
+// %7 = OpTypeFunction %1 %10
+// %8 = OpConstant %2 0.0
+// %9 = OpConstant %2 1.0
+// %11 = OpConstant %10 0
+// %12 = OpConstant %10 1
+// %13 = OpConstantComposite %3 %9 %8
+// %14 = OpConstantComposite %3 %8 %9
+// %15 = OpConstantComposite %4 %13 %14
+// %16 = OpFunction %1 None %7
+// %31 = OpFunctionParameter %10
+// %17 = OpLabel
+// %18 = OpVariable %5 Function %15
+// %19 = OpAccessChain %6 %18 %11 %12
+// OpStore %19 %8
+// %20 = OpAccessChain %6 %18 %11 %11
+// OpStore %20 %8
+// %21 = OpAccessChain %30 %18 %12
+// OpStore %21 %14
+// OpReturn
+// OpFunctionEnd
+// )";
+//
+// SinglePassRunAndMatch<opt::ScalarReplacementPass>(text, true);
+// }
+
+TEST_F(ScalarReplacementTest, ElideAccessChain) {
+ const std::string text = R"(
+;
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK-NOT: OpAccessChain
+; CHECK: OpStore [[var]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %6 "elide_access_chain"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpTypeStruct %2 %2 %2 %2
+%4 = OpTypePointer Function %3
+%20 = OpTypePointer Function %2
+%6 = OpTypeFunction %1
+%7 = OpConstant %2 0
+%8 = OpUndef %2
+%9 = OpConstant %2 2
+%10 = OpConstantNull %2
+%11 = OpConstantComposite %3 %7 %8 %9 %10
+%12 = OpFunction %1 None %6
+%13 = OpLabel
+%14 = OpVariable %4 Function %11
+%15 = OpAccessChain %20 %14 %7
+OpStore %15 %10
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ElideMultipleAccessChains) {
+ const std::string text = R"(
+;
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK-NOT: OpInBoundsAccessChain
+; CHECK OpStore [[var]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %6 "elide_two_access_chains"
+%1 = OpTypeVoid
+%2 = OpTypeFloat 32
+%3 = OpTypeStruct %2 %2
+%4 = OpTypeStruct %3 %3
+%5 = OpTypePointer Function %4
+%6 = OpTypePointer Function %2
+%7 = OpTypeFunction %1
+%8 = OpConstant %2 0.0
+%9 = OpConstant %2 1.0
+%10 = OpTypeInt 32 0
+%11 = OpConstant %10 0
+%12 = OpConstant %10 1
+%13 = OpConstantComposite %3 %9 %8
+%14 = OpConstantComposite %3 %8 %9
+%15 = OpConstantComposite %4 %13 %14
+%16 = OpFunction %1 None %7
+%17 = OpLabel
+%18 = OpVariable %5 Function %15
+%19 = OpInBoundsAccessChain %6 %18 %11 %12
+OpStore %19 %8
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ReplaceAccessChain) {
+ const std::string text = R"(
+;
+; CHECK: [[param:%\w+]] = OpFunctionParameter
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: [[access:%\w+]] = OpAccessChain {{%\w+}} [[var]] [[param]]
+; CHECK: OpStore [[access]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %7 "replace_access_chain"
+%1 = OpTypeVoid
+%2 = OpTypeFloat 32
+%10 = OpTypeInt 32 0
+%uint_2 = OpConstant %10 2
+%3 = OpTypeArray %2 %uint_2
+%4 = OpTypeStruct %3 %3
+%5 = OpTypePointer Function %4
+%20 = OpTypePointer Function %3
+%6 = OpTypePointer Function %2
+%7 = OpTypeFunction %1 %10
+%8 = OpConstant %2 0.0
+%9 = OpConstant %2 1.0
+%11 = OpConstant %10 0
+%12 = OpConstant %10 1
+%13 = OpConstantComposite %3 %9 %8
+%14 = OpConstantComposite %3 %8 %9
+%15 = OpConstantComposite %4 %13 %14
+%16 = OpFunction %1 None %7
+%32 = OpFunctionParameter %10
+%17 = OpLabel
+%18 = OpVariable %5 Function %15
+%19 = OpAccessChain %6 %18 %11 %32
+OpStore %19 %8
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ArrayInitialization) {
+ const std::string text = R"(
+;
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[array:%\w+]] = OpTypeArray
+; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]]
+; CHECK: [[float_ptr:%\w+]] = OpTypePointer Function [[float]]
+; CHECK: [[float0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[float1:%\w+]] = OpConstant [[float]] 1
+; CHECK: [[float2:%\w+]] = OpConstant [[float]] 2
+; CHECK-NOT: OpVariable [[array_ptr]]
+; CHECK: [[var0:%\w+]] = OpVariable [[float_ptr]] Function [[float0]]
+; CHECK-NEXT: [[var1:%\w+]] = OpVariable [[float_ptr]] Function [[float1]]
+; CHECK-NEXT: [[var2:%\w+]] = OpVariable [[float_ptr]] Function [[float2]]
+; CHECK-NOT: OpVariable [[array_ptr]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "array_init"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%float = OpTypeFloat 32
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%uint_3 = OpConstant %uint 3
+%float_array = OpTypeArray %float %uint_3
+%array_ptr = OpTypePointer Function %float_array
+%float_ptr = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%float_2 = OpConstant %float 2
+%const_array = OpConstantComposite %float_array %float_2 %float_1 %float_0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%3 = OpVariable %array_ptr Function %const_array
+%4 = OpInBoundsAccessChain %float_ptr %3 %uint_0
+OpStore %4 %float_0
+%5 = OpInBoundsAccessChain %float_ptr %3 %uint_1
+OpStore %5 %float_0
+%6 = OpInBoundsAccessChain %float_ptr %3 %uint_2
+OpStore %6 %float_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, NonUniformCompositeInitialization) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[long:%\w+]] = OpTypeInt 64 1
+; CHECK: [[dvector:%\w+]] = OpTypeVector
+; CHECK: [[vector:%\w+]] = OpTypeVector
+; CHECK: [[array:%\w+]] = OpTypeArray
+; CHECK: [[matrix:%\w+]] = OpTypeMatrix
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[vector]]
+; CHECK: [[struct2:%\w+]] = OpTypeStruct [[struct1]] [[matrix]] [[array]] [[uint]]
+; CHECK: [[struct1_ptr:%\w+]] = OpTypePointer Function [[struct1]]
+; CHECK: [[matrix_ptr:%\w+]] = OpTypePointer Function [[matrix]]
+; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[struct2_ptr:%\w+]] = OpTypePointer Function [[struct2]]
+; CHECK: [[const_array:%\w+]] = OpConstantComposite [[array]]
+; CHECK: [[const_matrix:%\w+]] = OpConstantNull [[matrix]]
+; CHECK: [[const_struct1:%\w+]] = OpConstantComposite [[struct1]]
+; CHECK: OpConstantNull [[uint]]
+; CHECK: OpConstantNull [[vector]]
+; CHECK: OpConstantNull [[long]]
+; CHECK: OpFunction
+; CHECK-NOT: OpVariable [[struct2_ptr]] Function
+; CHECK: OpVariable [[uint_ptr]] Function
+; CHECK-NEXT: OpVariable [[matrix_ptr]] Function [[const_matrix]]
+; CHECK-NOT: OpVariable [[struct1_ptr]] Function [[const_struct1]]
+; CHECK-NOT: OpVariable [[struct2_ptr]] Function
+;
+OpCapability Shader
+OpCapability Linkage
+OpCapability Int64
+OpCapability Float64
+OpMemoryModel Logical GLSL450
+OpName %func "non_uniform_composite_init"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%int64 = OpTypeInt 64 1
+%float = OpTypeFloat 32
+%double = OpTypeFloat 64
+%double2 = OpTypeVector %double 2
+%float4 = OpTypeVector %float 4
+%int64_0 = OpConstant %int64 0
+%int64_1 = OpConstant %int64 1
+%int64_2 = OpConstant %int64 2
+%int64_3 = OpConstant %int64 3
+%int64_array3 = OpTypeArray %int64 %int64_3
+%matrix_double2 = OpTypeMatrix %double2 2
+%struct1 = OpTypeStruct %uint %float4
+%struct2 = OpTypeStruct %struct1 %matrix_double2 %int64_array3 %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%matrix_double2_ptr = OpTypePointer Function %matrix_double2
+%int64_array_ptr = OpTypePointer Function %int64_array3
+%uint_ptr = OpTypePointer Function %uint
+%struct2_ptr = OpTypePointer Function %struct2
+%const_uint = OpConstant %uint 0
+%const_int64_array = OpConstantComposite %int64_array3 %int64_0 %int64_1 %int64_2
+%const_double2 = OpConstantNull %double2
+%const_matrix_double2 = OpConstantNull %matrix_double2
+%undef_float4 = OpUndef %float4
+%const_struct1 = OpConstantComposite %struct1 %const_uint %undef_float4
+%const_struct2 = OpConstantComposite %struct2 %const_struct1 %const_matrix_double2 %const_int64_array %const_uint
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct2_ptr Function %const_struct2
+%3 = OpAccessChain %struct1_ptr %var %int64_0
+OpStore %3 %const_struct1
+%4 = OpAccessChain %matrix_double2_ptr %var %int64_1
+OpStore %4 %const_matrix_double2
+%5 = OpAccessChain %int64_array_ptr %var %int64_2
+OpStore %5 %const_int64_array
+%6 = OpAccessChain %uint_ptr %var %int64_3
+OpStore %6 %const_uint
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ElideUncombinedAccessChains) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[var:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK-NOT: OpAccessChain
+; CHECK: OpStore [[var]] [[const]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "elide_uncombined_access_chains"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%struct2 = OpTypeStruct %struct1
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%struct2_ptr = OpTypePointer Function %struct2
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct2_ptr Function
+%3 = OpAccessChain %struct1_ptr %var %uint_0
+%4 = OpAccessChain %uint_ptr %3 %uint_0
+OpStore %4 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ElideSingleUncombinedAccessChains) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[array:%\w+]] = OpTypeArray [[uint]]
+; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[param:%\w+]] = OpFunctionParameter [[uint]]
+; CHECK: [[var:%\w+]] = OpVariable [[array_ptr]] Function
+; CHECK: [[access:%\w+]] = OpAccessChain {{.*}} [[var]] [[param]]
+; CHECK: OpStore [[access]] [[const]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "elide_single_uncombined_access_chains"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%array = OpTypeArray %uint %uint_1
+%struct2 = OpTypeStruct %array
+%uint_ptr = OpTypePointer Function %uint
+%array_ptr = OpTypePointer Function %array
+%struct2_ptr = OpTypePointer Function %struct2
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void %uint
+%1 = OpFunction %void None %func
+%param = OpFunctionParameter %uint
+%2 = OpLabel
+%var = OpVariable %struct2_ptr Function
+%3 = OpAccessChain %array_ptr %var %uint_0
+%4 = OpAccessChain %uint_ptr %3 %param
+OpStore %4 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ReplaceWholeLoad) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
+; CHECK: [[l0:%\w+]] = OpLoad [[uint]] [[var0]]
+; CHECK: OpCompositeConstruct [[struct1]] [[l0]] [[l1]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_load"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%load = OpLoad %struct1 %var
+%3 = OpAccessChain %uint_ptr %var %uint_0
+OpStore %3 %uint_0
+%4 = OpAccessChain %uint_ptr %var %uint_1
+OpStore %4 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ReplaceWholeLoadCopyMemoryAccess) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[null:%\w+]] = OpConstantNull [[uint]]
+; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[l0:%\w+]] = OpLoad [[uint]] [[var0]] Nontemporal
+; CHECK: OpCompositeConstruct [[struct1]] [[l0]] [[null]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_load_copy_memory_access"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%load = OpLoad %struct1 %var Nontemporal
+%3 = OpAccessChain %uint_ptr %var %uint_0
+OpStore %3 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ReplaceWholeStore) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[const_struct:%\w+]] = OpConstantComposite [[struct1]] [[const]] [[const]]
+; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[uint]] [[const_struct]] 0
+; CHECK: OpStore [[var0]] [[ex0]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_store"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%const_struct = OpConstantComposite %struct1 %uint_0 %uint_0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+OpStore %var %const_struct
+%3 = OpAccessChain %uint_ptr %var %uint_0
+%4 = OpLoad %uint %3
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ReplaceWholeStoreCopyMemoryAccess) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[const_struct:%\w+]] = OpConstantComposite [[struct1]] [[const]] [[const]]
+; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK-NOT: OpVariable
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[uint]] [[const_struct]] 0
+; CHECK: OpStore [[var0]] [[ex0]] Aligned 4
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_store_copy_memory_access"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%const_struct = OpConstantComposite %struct1 %uint_0 %uint_0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+OpStore %var %const_struct Aligned 4
+%3 = OpAccessChain %uint_ptr %var %uint_0
+%4 = OpLoad %uint %3
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DontTouchVolatileLoad) {
+ const std::string text = R"(
+;
+; CHECK: [[struct:%\w+]] = OpTypeStruct
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[struct_ptr]]
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "dont_touch_volatile_load"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpAccessChain %uint_ptr %var %uint_0
+%4 = OpLoad %uint %3 Volatile
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DontTouchVolatileStore) {
+ const std::string text = R"(
+;
+; CHECK: [[struct:%\w+]] = OpTypeStruct
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[struct_ptr]]
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "dont_touch_volatile_store"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpAccessChain %uint_ptr %var %uint_0
+OpStore %3 %uint_0 Volatile
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DontTouchSpecNonFunctionVariable) {
+ const std::string text = R"(
+;
+; CHECK: [[struct:%\w+]] = OpTypeStruct
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Uniform [[struct]]
+; CHECK: OpConstant
+; CHECK-NEXT: OpVariable [[struct_ptr]]
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "dont_touch_spec_constant_access_chain"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Uniform %uint
+%struct1_ptr = OpTypePointer Uniform %struct1
+%uint_0 = OpConstant %uint 0
+%var = OpVariable %struct1_ptr Uniform
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%3 = OpAccessChain %uint_ptr %var %uint_0
+OpStore %3 %uint_0 Volatile
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DontTouchSpecConstantAccessChain) {
+ const std::string text = R"(
+;
+; CHECK: [[array:%\w+]] = OpTypeArray
+; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[array_ptr]]
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "dont_touch_spec_constant_access_chain"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%array = OpTypeArray %uint %uint_1
+%uint_ptr = OpTypePointer Function %uint
+%array_ptr = OpTypePointer Function %array
+%uint_0 = OpConstant %uint 0
+%spec_const = OpSpecConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %array_ptr Function
+%3 = OpAccessChain %uint_ptr %var %spec_const
+OpStore %3 %uint_0 Volatile
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, NoPartialAccesses) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: OpLabel
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "no_partial_accesses"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%const = OpConstantNull %struct1
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+OpStore %var %const
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DontTouchPtrAccessChain) {
+ const std::string text = R"(
+;
+; CHECK: [[struct:%\w+]] = OpTypeStruct
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[struct_ptr]]
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "dont_touch_ptr_access_chain"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpPtrAccessChain %uint_ptr %var %uint_0 %uint_0
+OpStore %3 %uint_0
+%4 = OpAccessChain %uint_ptr %var %uint_0
+OpStore %4 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, false);
+}
+
+TEST_F(ScalarReplacementTest, DontTouchInBoundsPtrAccessChain) {
+ const std::string text = R"(
+;
+; CHECK: [[struct:%\w+]] = OpTypeStruct
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[struct_ptr]]
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "dont_touch_in_bounds_ptr_access_chain"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpInBoundsPtrAccessChain %uint_ptr %var %uint_0 %uint_0
+OpStore %3 %uint_0
+%4 = OpInBoundsAccessChain %uint_ptr %var %uint_0
+OpStore %4 %uint_0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, false);
+}
+
+TEST_F(ScalarReplacementTest, DonTouchAliasedDecoration) {
+ const std::string text = R"(
+;
+; CHECK: [[struct:%\w+]] = OpTypeStruct
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[struct_ptr]]
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "aliased"
+OpDecorate %var Aliased
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpAccessChain %uint_ptr %var %uint_0
+%4 = OpLoad %uint %3
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, CopyRestrictDecoration) {
+ const std::string text = R"(
+;
+; CHECK: OpName
+; CHECK-NEXT: OpDecorate [[var0:%\w+]] Restrict
+; CHECK-NEXT: OpDecorate [[var1:%\w+]] Restrict
+; CHECK: [[int:%\w+]] = OpTypeInt
+; CHECK: [[struct:%\w+]] = OpTypeStruct
+; CHECK: [[int_ptr:%\w+]] = OpTypePointer Function [[int]]
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: OpLabel
+; CHECK-NEXT: [[var1]] = OpVariable [[int_ptr]]
+; CHECK-NEXT: [[var0]] = OpVariable [[int_ptr]]
+; CHECK-NOT: OpVariable [[struct_ptr]]
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "restrict"
+OpDecorate %var Restrict
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpAccessChain %uint_ptr %var %uint_0
+%4 = OpLoad %uint %3
+%5 = OpAccessChain %uint_ptr %var %uint_1
+%6 = OpLoad %uint %5
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DontClobberDecoratesOnSubtypes) {
+ const std::string text = R"(
+;
+; CHECK: OpDecorate [[array:%\w+]] ArrayStride 1
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[array]] = OpTypeArray [[uint]]
+; CHECK: [[array_ptr:%\w+]] = OpTypePointer Function [[array]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[array_ptr]] Function
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "array_stride"
+OpDecorate %array ArrayStride 1
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%array = OpTypeArray %uint %uint_1
+%struct1 = OpTypeStruct %array
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void %uint
+%1 = OpFunction %void None %func
+%param = OpFunctionParameter %uint
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpAccessChain %uint_ptr %var %uint_0 %param
+%4 = OpLoad %uint %3
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, DontCopyMemberDecorate) {
+ const std::string text = R"(
+;
+; CHECK-NOT: OpDecorate
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[uint]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[struct_ptr:%\w+]] = OpTypePointer Function [[struct]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[uint_ptr]] Function
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "member_decorate"
+OpMemberDecorate %struct1 0 Offset 1
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_1 = OpConstant %uint 1
+%struct1 = OpTypeStruct %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%func = OpTypeFunction %void %uint
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct1_ptr Function
+%3 = OpAccessChain %uint_ptr %var %uint_0
+%4 = OpLoad %uint %3
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, NoPartialAccesses2) {
+ const std::string text = R"(
+;
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_ptr:%\w+]] = OpTypePointer Function [[float]]
+; CHECK: OpVariable [[float_ptr]] Function
+; CHECK: OpVariable [[float_ptr]] Function
+; CHECK: OpVariable [[float_ptr]] Function
+; CHECK: OpVariable [[float_ptr]] Function
+; CHECK: OpVariable [[float_ptr]] Function
+; CHECK: OpVariable [[float_ptr]] Function
+; CHECK: OpVariable [[float_ptr]] Function
+; CHECK-NOT: OpVariable
+;
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %fo
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 430
+OpName %main "main"
+OpName %S "S"
+OpMemberName %S 0 "x"
+OpMemberName %S 1 "y"
+OpName %ts1 "ts1"
+OpName %S_0 "S"
+OpMemberName %S_0 0 "x"
+OpMemberName %S_0 1 "y"
+OpName %U_t "U_t"
+OpMemberName %U_t 0 "g_s1"
+OpMemberName %U_t 1 "g_s2"
+OpMemberName %U_t 2 "g_s3"
+OpName %_ ""
+OpName %ts2 "ts2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpName %__0 ""
+OpName %ts3 "ts3"
+OpName %ts4 "ts4"
+OpName %fo "fo"
+OpMemberDecorate %S_0 0 Offset 0
+OpMemberDecorate %S_0 1 Offset 4
+OpMemberDecorate %U_t 0 Offset 0
+OpMemberDecorate %U_t 1 Offset 8
+OpMemberDecorate %U_t 2 Offset 16
+OpDecorate %U_t BufferBlock
+OpDecorate %_ DescriptorSet 0
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpDecorate %_Globals_ Block
+OpDecorate %__0 DescriptorSet 0
+OpDecorate %__0 Binding 0
+OpDecorate %fo Location 0
+%void = OpTypeVoid
+%15 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%S = OpTypeStruct %float %float
+%_ptr_Function_S = OpTypePointer Function %S
+%S_0 = OpTypeStruct %float %float
+%U_t = OpTypeStruct %S_0 %S_0 %S_0
+%_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_S_0 = OpTypePointer Uniform %S_0
+%_ptr_Function_float = OpTypePointer Function %float
+%int_1 = OpConstant %int 1
+%uint = OpTypeInt 32 0
+%_Globals_ = OpTypeStruct %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%__0 = OpVariable %_ptr_Uniform__Globals_ Uniform
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%_ptr_Output_float = OpTypePointer Output %float
+%fo = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %15
+%30 = OpLabel
+%ts1 = OpVariable %_ptr_Function_S Function
+%ts2 = OpVariable %_ptr_Function_S Function
+%ts3 = OpVariable %_ptr_Function_S Function
+%ts4 = OpVariable %_ptr_Function_S Function
+%31 = OpAccessChain %_ptr_Uniform_S_0 %_ %int_0
+%32 = OpLoad %S_0 %31
+%33 = OpCompositeExtract %float %32 0
+%34 = OpAccessChain %_ptr_Function_float %ts1 %int_0
+OpStore %34 %33
+%35 = OpCompositeExtract %float %32 1
+%36 = OpAccessChain %_ptr_Function_float %ts1 %int_1
+OpStore %36 %35
+%37 = OpAccessChain %_ptr_Uniform_S_0 %_ %int_1
+%38 = OpLoad %S_0 %37
+%39 = OpCompositeExtract %float %38 0
+%40 = OpAccessChain %_ptr_Function_float %ts2 %int_0
+OpStore %40 %39
+%41 = OpCompositeExtract %float %38 1
+%42 = OpAccessChain %_ptr_Function_float %ts2 %int_1
+OpStore %42 %41
+%43 = OpAccessChain %_ptr_Uniform_uint %__0 %int_0
+%44 = OpLoad %uint %43
+%45 = OpINotEqual %bool %44 %uint_0
+OpSelectionMerge %46 None
+OpBranchConditional %45 %47 %48
+%47 = OpLabel
+%49 = OpLoad %S %ts1
+OpStore %ts3 %49
+OpBranch %46
+%48 = OpLabel
+%50 = OpLoad %S %ts2
+OpStore %ts3 %50
+OpBranch %46
+%46 = OpLabel
+%51 = OpLoad %S %ts3
+OpStore %ts4 %51
+%52 = OpAccessChain %_ptr_Function_float %ts4 %int_1
+%53 = OpLoad %float %52
+OpStore %fo %53
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ReplaceWholeLoadAndStore) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[null:%\w+]] = OpConstantNull [[uint]]
+; CHECK: [[var0:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK-NOT: OpVariable
+; CHECK: [[l0:%\w+]] = OpLoad [[uint]] [[var0]]
+; CHECK: [[c0:%\w+]] = OpCompositeConstruct [[struct1]] [[l0]] [[null]]
+; CHECK: [[e0:%\w+]] = OpCompositeExtract [[uint]] [[c0]] 0
+; CHECK: OpStore [[var1]] [[e0]]
+; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_load"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var2 = OpVariable %struct1_ptr Function
+%var1 = OpVariable %struct1_ptr Function
+%load1 = OpLoad %struct1 %var1
+OpStore %var2 %load1
+%load2 = OpLoad %struct1 %var2
+%3 = OpCompositeExtract %uint %load2 0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, ReplaceWholeLoadAndStore2) {
+ // TODO: We can improve this case by ensuring that |var2| is processed first.
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[uint]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[null:%\w+]] = OpConstantNull [[uint]]
+; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var0a:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var0b:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK-NOT: OpVariable
+; CHECK: [[l0a:%\w+]] = OpLoad [[uint]] [[var0a]]
+; CHECK: [[l0b:%\w+]] = OpLoad [[uint]] [[var0b]]
+; CHECK: [[c0:%\w+]] = OpCompositeConstruct [[struct1]] [[l0b]] [[l0a]]
+; CHECK: [[e0:%\w+]] = OpCompositeExtract [[uint]] [[c0]] 0
+; CHECK: OpStore [[var1]] [[e0]]
+; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_load"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct1 = OpTypeStruct %uint %uint
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var1 = OpVariable %struct1_ptr Function
+%var2 = OpVariable %struct1_ptr Function
+%load1 = OpLoad %struct1 %var1
+OpStore %var2 %load1
+%load2 = OpLoad %struct1 %var2
+%3 = OpCompositeExtract %uint %load2 0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, CreateAmbiguousNullConstant1) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[struct_member:%\w+]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[null:%\w+]] = OpConstantNull [[struct_member]]
+; CHECK: [[var0a:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var0b:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK-NOT: OpVariable
+; CHECK: OpStore [[var1]]
+; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_load"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct2 = OpTypeStruct %uint
+%struct3 = OpTypeStruct %uint
+%struct1 = OpTypeStruct %uint %struct2
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var1 = OpVariable %struct1_ptr Function
+%var2 = OpVariable %struct1_ptr Function
+%load1 = OpLoad %struct1 %var1
+OpStore %var2 %load1
+%load2 = OpLoad %struct1 %var2
+%3 = OpCompositeExtract %uint %load2 0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, SpecConstantArray) {
+ const std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt
+; CHECK: [[spec_const:%\w+]] = OpSpecConstant [[int]] 4
+; CHECK: [[spec_op:%\w+]] = OpSpecConstantOp [[int]] IAdd [[spec_const]] [[spec_const]]
+; CHECK: [[array1:%\w+]] = OpTypeArray [[int]] [[spec_const]]
+; CHECK: [[array2:%\w+]] = OpTypeArray [[int]] [[spec_op]]
+; CHECK: [[ptr_array1:%\w+]] = OpTypePointer Function [[array1]]
+; CHECK: [[ptr_array2:%\w+]] = OpTypePointer Function [[array2]]
+; CHECK: OpLabel
+; CHECK-NEXT: OpVariable [[ptr_array1]] Function
+; CHECK-NEXT: OpVariable [[ptr_array2]] Function
+; CHECK-NOT: OpVariable
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%int = OpTypeInt 32 0
+%spec_const = OpSpecConstant %int 4
+%spec_op = OpSpecConstantOp %int IAdd %spec_const %spec_const
+%array_1 = OpTypeArray %int %spec_const
+%array_2 = OpTypeArray %int %spec_op
+%ptr_array_1_Function = OpTypePointer Function %array_1
+%ptr_array_2_Function = OpTypePointer Function %array_2
+%func = OpFunction %void None %void_fn
+%1 = OpLabel
+%var_1 = OpVariable %ptr_array_1_Function Function
+%var_2 = OpVariable %ptr_array_2_Function Function
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+TEST_F(ScalarReplacementTest, CreateAmbiguousNullConstant2) {
+ const std::string text = R"(
+;
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct1:%\w+]] = OpTypeStruct [[uint]] [[struct_member:%\w+]]
+; CHECK: [[uint_ptr:%\w+]] = OpTypePointer Function [[uint]]
+; CHECK: [[const:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[null:%\w+]] = OpConstantNull [[struct_member]]
+; CHECK: [[var0a:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var1:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: [[var0b:%\w+]] = OpVariable [[uint_ptr]] Function
+; CHECK: OpStore [[var1]]
+; CHECK: [[l1:%\w+]] = OpLoad [[uint]] [[var1]]
+; CHECK: [[c1:%\w+]] = OpCompositeConstruct [[struct1]] [[l1]] [[null]]
+; CHECK: [[e1:%\w+]] = OpCompositeExtract [[uint]] [[c1]] 0
+;
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %func "replace_whole_load"
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%struct3 = OpTypeStruct %uint
+%struct2 = OpTypeStruct %uint
+%struct1 = OpTypeStruct %uint %struct2
+%uint_ptr = OpTypePointer Function %uint
+%struct1_ptr = OpTypePointer Function %struct1
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%func = OpTypeFunction %void
+%1 = OpFunction %void None %func
+%2 = OpLabel
+%var1 = OpVariable %struct1_ptr Function
+%var2 = OpVariable %struct1_ptr Function
+%load1 = OpLoad %struct1 %var1
+OpStore %var2 %load1
+%load2 = OpLoad %struct1 %var2
+%3 = OpCompositeExtract %uint %load2 0
+OpReturn
+OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+// Test that a struct of size 4 is not replaced when there is a limit of 2.
+TEST_F(ScalarReplacementTest, TestLimit) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %6 "simple_struct"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpTypeStruct %2 %2 %2 %2
+%4 = OpTypePointer Function %3
+%5 = OpTypePointer Function %2
+%6 = OpTypeFunction %2
+%7 = OpConstantNull %3
+%8 = OpConstant %2 0
+%9 = OpConstant %2 1
+%10 = OpConstant %2 2
+%11 = OpConstant %2 3
+%12 = OpFunction %2 None %6
+%13 = OpLabel
+%14 = OpVariable %4 Function %7
+%15 = OpInBoundsAccessChain %5 %14 %8
+%16 = OpLoad %2 %15
+%17 = OpAccessChain %5 %14 %10
+%18 = OpLoad %2 %17
+%19 = OpIAdd %2 %16 %18
+OpReturnValue %19
+OpFunctionEnd
+ )";
+
+ auto result =
+ SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false, 2);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// Test that a struct of size 4 is replaced when there is a limit of 0 (no
+// limit). This is the same spir-v as a test above, so we do not check that it
+// is correctly transformed. We leave that to the test above.
+TEST_F(ScalarReplacementTest, TestUnimited) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpName %6 "simple_struct"
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpTypeStruct %2 %2 %2 %2
+%4 = OpTypePointer Function %3
+%5 = OpTypePointer Function %2
+%6 = OpTypeFunction %2
+%7 = OpConstantNull %3
+%8 = OpConstant %2 0
+%9 = OpConstant %2 1
+%10 = OpConstant %2 2
+%11 = OpConstant %2 3
+%12 = OpFunction %2 None %6
+%13 = OpLabel
+%14 = OpVariable %4 Function %7
+%15 = OpInBoundsAccessChain %5 %14 %8
+%16 = OpLoad %2 %15
+%17 = OpAccessChain %5 %14 %10
+%18 = OpLoad %2 %17
+%19 = OpIAdd %2 %16 %18
+OpReturnValue %19
+OpFunctionEnd
+ )";
+
+ auto result =
+ SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false, 0);
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+}
+
+TEST_F(ScalarReplacementTest, AmbigousPointer) {
+ const std::string text = R"(
+; CHECK: [[s1:%\w+]] = OpTypeStruct %uint
+; CHECK: [[s2:%\w+]] = OpTypeStruct %uint
+; CHECK: [[s3:%\w+]] = OpTypeStruct [[s2]]
+; CHECK: [[s3_const:%\w+]] = OpConstantComposite [[s3]]
+; CHECK: [[s2_ptr:%\w+]] = OpTypePointer Function [[s2]]
+; CHECK: OpCompositeExtract [[s2]] [[s3_const]]
+
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %void = OpTypeVoid
+ %5 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %_struct_7 = OpTypeStruct %uint
+ %_struct_8 = OpTypeStruct %uint
+ %_struct_9 = OpTypeStruct %_struct_8
+ %uint_1 = OpConstant %uint 1
+ %11 = OpConstantComposite %_struct_8 %uint_1
+ %12 = OpConstantComposite %_struct_9 %11
+%_ptr_Function__struct_9 = OpTypePointer Function %_struct_9
+%_ptr_Function__struct_7 = OpTypePointer Function %_struct_7
+ %2 = OpFunction %void None %5
+ %15 = OpLabel
+ %var = OpVariable %_ptr_Function__struct_9 Function
+ OpStore %var %12
+ %ld = OpLoad %_struct_9 %var
+ %ex = OpCompositeExtract %_struct_8 %ld 0
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ SinglePassRunAndMatch<ScalarReplacementPass>(text, true);
+}
+
+// Test that scalar replacement does not crash when there is an OpAccessChain
+// with no index. If we choose to handle this case in the future, then the
+// result can change.
+TEST_F(ScalarReplacementTest, TestAccessChainWithNoIndexes) {
+ const std::string text = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "main"
+ OpExecutionMode %1 OriginLowerLeft
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %_struct_5 = OpTypeStruct %float
+%_ptr_Function__struct_5 = OpTypePointer Function %_struct_5
+ %1 = OpFunction %void None %3
+ %7 = OpLabel
+ %8 = OpVariable %_ptr_Function__struct_5 Function
+ %9 = OpAccessChain %_ptr_Function__struct_5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ auto result =
+ SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/set_spec_const_default_value_test.cpp b/test/opt/set_spec_const_default_value_test.cpp
new file mode 100644
index 0000000..161674f
--- /dev/null
+++ b/test/opt/set_spec_const_default_value_test.cpp
@@ -0,0 +1,1077 @@
+// Copyright (c) 2016 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 <vector>
+
+#include "gmock/gmock.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using testing::Eq;
+using SpecIdToValueStrMap =
+ SetSpecConstantDefaultValuePass::SpecIdToValueStrMap;
+using SpecIdToValueBitPatternMap =
+ SetSpecConstantDefaultValuePass::SpecIdToValueBitPatternMap;
+
+struct DefaultValuesStringParsingTestCase {
+ const char* default_values_str;
+ bool expect_success;
+ SpecIdToValueStrMap expected_map;
+};
+
+using DefaultValuesStringParsingTest =
+ ::testing::TestWithParam<DefaultValuesStringParsingTestCase>;
+
+TEST_P(DefaultValuesStringParsingTest, TestCase) {
+ const auto& tc = GetParam();
+ auto actual_map = SetSpecConstantDefaultValuePass::ParseDefaultValuesString(
+ tc.default_values_str);
+ if (tc.expect_success) {
+ EXPECT_NE(nullptr, actual_map);
+ if (actual_map) {
+ EXPECT_THAT(*actual_map, Eq(tc.expected_map));
+ }
+ } else {
+ EXPECT_EQ(nullptr, actual_map);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ValidString, DefaultValuesStringParsingTest,
+ ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
+ // 0. empty map
+ {"", true, SpecIdToValueStrMap{}},
+ // 1. one pair
+ {"100:1024", true, SpecIdToValueStrMap{{100, "1024"}}},
+ // 2. two pairs
+ {"100:1024 200:2048", true,
+ SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}},
+ // 3. spaces between entries
+ {"100:1024 \n \r \t \v \f 200:2048", true,
+ SpecIdToValueStrMap{{100, "1024"}, {200, "2048"}}},
+ // 4. \t, \n, \r and spaces before spec id
+ {" \n \r\t \t \v \f 100:1024", true,
+ SpecIdToValueStrMap{{100, "1024"}}},
+ // 5. \t, \n, \r and spaces after value string
+ {"100:1024 \n \r\t \t \v \f ", true,
+ SpecIdToValueStrMap{{100, "1024"}}},
+ // 6. maximum spec id
+ {"4294967295:0", true, SpecIdToValueStrMap{{4294967295, "0"}}},
+ // 7. minimum spec id
+ {"0:100", true, SpecIdToValueStrMap{{0, "100"}}},
+ // 8. random content without spaces are allowed
+ {"200:random_stuff", true, SpecIdToValueStrMap{{200, "random_stuff"}}},
+ // 9. support hex format spec id (just because we use the
+ // ParseNumber() utility)
+ {"0x100:1024", true, SpecIdToValueStrMap{{256, "1024"}}},
+ // 10. multiple entries
+ {"101:1 102:2 103:3 104:4 200:201 9999:1000 0x100:333", true,
+ SpecIdToValueStrMap{{101, "1"},
+ {102, "2"},
+ {103, "3"},
+ {104, "4"},
+ {200, "201"},
+ {9999, "1000"},
+ {256, "333"}}},
+ // 11. default value in hex float format
+ {"100:0x0.3p10", true, SpecIdToValueStrMap{{100, "0x0.3p10"}}},
+ // 12. default value in decimal float format
+ {"100:1.5e-13", true, SpecIdToValueStrMap{{100, "1.5e-13"}}},
+ }));
+
+INSTANTIATE_TEST_CASE_P(
+ InvalidString, DefaultValuesStringParsingTest,
+ ::testing::ValuesIn(std::vector<DefaultValuesStringParsingTestCase>{
+ // 0. missing default value
+ {"100:", false, SpecIdToValueStrMap{}},
+ // 1. spec id is not an integer
+ {"100.0:200", false, SpecIdToValueStrMap{}},
+ // 2. spec id is not a number
+ {"something_not_a_number:1", false, SpecIdToValueStrMap{}},
+ // 3. only spec id number
+ {"100", false, SpecIdToValueStrMap{}},
+ // 4. same spec id defined multiple times
+ {"100:20 100:21", false, SpecIdToValueStrMap{}},
+ // 5. Multiple definition of an identical spec id in different forms
+ // is not allowed
+ {"0x100:100 256:200", false, SpecIdToValueStrMap{}},
+ // 6. empty spec id
+ {":3", false, SpecIdToValueStrMap{}},
+ // 7. only colon
+ {":", false, SpecIdToValueStrMap{}},
+ // 8. spec id overflow
+ {"4294967296:200", false, SpecIdToValueStrMap{}},
+ // 9. spec id less than 0
+ {"-1:200", false, SpecIdToValueStrMap{}},
+ // 10. nullptr
+ {nullptr, false, SpecIdToValueStrMap{}},
+ // 11. only a number is invalid
+ {"1234", false, SpecIdToValueStrMap{}},
+ // 12. invalid entry separator
+ {"12:34;23:14", false, SpecIdToValueStrMap{}},
+ // 13. invalid spec id and default value separator
+ {"12@34", false, SpecIdToValueStrMap{}},
+ // 14. spaces before colon
+ {"100 :1024", false, SpecIdToValueStrMap{}},
+ // 15. spaces after colon
+ {"100: 1024", false, SpecIdToValueStrMap{}},
+ // 16. spec id represented in hex float format is invalid
+ {"0x3p10:200", false, SpecIdToValueStrMap{}},
+ }));
+
+struct SetSpecConstantDefaultValueInStringFormTestCase {
+ const char* code;
+ SpecIdToValueStrMap default_values;
+ const char* expected;
+};
+
+using SetSpecConstantDefaultValueInStringFormParamTest = PassTest<
+ ::testing::TestWithParam<SetSpecConstantDefaultValueInStringFormTestCase>>;
+
+TEST_P(SetSpecConstantDefaultValueInStringFormParamTest, TestCase) {
+ const auto& tc = GetParam();
+ SinglePassRunAndCheck<SetSpecConstantDefaultValuePass>(
+ tc.code, tc.expected, /* skip_nop = */ false, tc.default_values);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ValidCases, SetSpecConstantDefaultValueInStringFormParamTest,
+ ::testing::ValuesIn(std::vector<
+ SetSpecConstantDefaultValueInStringFormTestCase>{
+ // 0. Empty.
+ {"", SpecIdToValueStrMap{}, ""},
+ // 1. Empty with non-empty values to set.
+ {"", SpecIdToValueStrMap{{1, "100"}, {2, "200"}}, ""},
+ // 2. Bool type.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n",
+ // default values
+ SpecIdToValueStrMap{{100, "false"}, {101, "true"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantFalse %bool\n"
+ "%2 = OpSpecConstantTrue %bool\n",
+ },
+ // 3. 32-bit int type.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 10\n"
+ "%2 = OpSpecConstant %int 11\n"
+ "%3 = OpSpecConstant %int 11\n",
+ // default values
+ SpecIdToValueStrMap{
+ {100, "2147483647"}, {101, "0xffffffff"}, {102, "-42"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 2147483647\n"
+ "%2 = OpSpecConstant %int -1\n"
+ "%3 = OpSpecConstant %int -42\n",
+ },
+ // 4. 64-bit uint type.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %ulong 10\n"
+ "%2 = OpSpecConstant %ulong 11\n",
+ // default values
+ SpecIdToValueStrMap{{100, "18446744073709551614"}, {101, "0x100"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %ulong 18446744073709551614\n"
+ "%2 = OpSpecConstant %ulong 256\n",
+ },
+ // 5. 32-bit float type.
+ {
+ // code
+ "OpDecorate %1 SpecId 101\n"
+ "OpDecorate %2 SpecId 102\n"
+ "%float = OpTypeFloat 32\n"
+ "%1 = OpSpecConstant %float 200\n"
+ "%2 = OpSpecConstant %float 201\n",
+ // default values
+ SpecIdToValueStrMap{{101, "-0x1.fffffep+128"}, {102, "2.5"}},
+ // expected
+ "OpDecorate %1 SpecId 101\n"
+ "OpDecorate %2 SpecId 102\n"
+ "%float = OpTypeFloat 32\n"
+ "%1 = OpSpecConstant %float -0x1.fffffep+128\n"
+ "%2 = OpSpecConstant %float 2.5\n",
+ },
+ // 6. 64-bit float type.
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n"
+ "%2 = OpSpecConstant %double 0.14285\n",
+ // default values
+ SpecIdToValueStrMap{{201, "0x1.fffffffffffffp+1024"},
+ {202, "-32.5"}},
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n"
+ "%2 = OpSpecConstant %double -32.5\n",
+ },
+ // 7. SpecId not found, expect no modification.
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n",
+ // default values
+ SpecIdToValueStrMap{{8888, "0.0"}},
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n",
+ },
+ // 8. Multiple types of spec constants.
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "OpDecorate %3 SpecId 203\n"
+ "%bool = OpTypeBool\n"
+ "%int = OpTypeInt 32 1\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n"
+ "%2 = OpSpecConstant %int 1024\n"
+ "%3 = OpSpecConstantTrue %bool\n",
+ // default values
+ SpecIdToValueStrMap{
+ {201, "0x1.fffffffffffffp+1024"},
+ {202, "2048"},
+ {203, "false"},
+ },
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "OpDecorate %3 SpecId 203\n"
+ "%bool = OpTypeBool\n"
+ "%int = OpTypeInt 32 1\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n"
+ "%2 = OpSpecConstant %int 2048\n"
+ "%3 = OpSpecConstantFalse %bool\n",
+ },
+ // 9. Ignore other decorations.
+ {
+ // code
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{4, "0x7fffffff"}},
+ // expected
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 100\n",
+ },
+ // 10. Distinguish from other decorations.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{4, "0x7fffffff"}, {100, "0xffffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int -1\n",
+ },
+ // 11. Decorate through decoration group.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0x7fffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 2147483647\n",
+ },
+ // 12. Ignore other decorations in decoration group.
+ {
+ // code
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{4, "0x7fffffff"}},
+ // expected
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ },
+ // 13. Distinguish from other decorations in decoration group.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0x7fffffff"}, {4, "0x00000001"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 2147483647\n",
+ },
+ // 14. Unchanged bool default value
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n",
+ // default values
+ SpecIdToValueStrMap{{100, "true"}, {101, "false"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n",
+ },
+ // 15. Unchanged int default values
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%int = OpTypeInt 32 1\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %int 10\n"
+ "%2 = OpSpecConstant %ulong 11\n",
+ // default values
+ SpecIdToValueStrMap{{100, "10"}, {101, "11"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%int = OpTypeInt 32 1\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %int 10\n"
+ "%2 = OpSpecConstant %ulong 11\n",
+ },
+ // 16. Unchanged float default values
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%float = OpTypeFloat 32\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %float 3.1415\n"
+ "%2 = OpSpecConstant %double 0.14285\n",
+ // default values
+ SpecIdToValueStrMap{{201, "3.1415"}, {202, "0.14285"}},
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%float = OpTypeFloat 32\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %float 3.1415\n"
+ "%2 = OpSpecConstant %double 0.14285\n",
+ },
+ // 17. OpGroupDecorate may have multiple target ids defined by the same
+ // eligible spec constant
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %2 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0xffffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %2 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int -1\n",
+ },
+ }));
+
+INSTANTIATE_TEST_CASE_P(
+ InvalidCases, SetSpecConstantDefaultValueInStringFormParamTest,
+ ::testing::ValuesIn(std::vector<
+ SetSpecConstantDefaultValueInStringFormTestCase>{
+ // 0. Do not crash when decoration group is not used.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0x7fffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 100\n",
+ },
+ // 1. Do not crash when target does not exist.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0x7fffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n",
+ },
+ // 2. Do nothing when SpecId decoration is not attached to a
+ // non-spec-constant instruction.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_101 = OpConstant %int 101\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0x7fffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_101 = OpConstant %int 101\n",
+ },
+ // 3. Do nothing when SpecId decoration is not attached to a
+ // OpSpecConstant{|True|False} instruction.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 101\n"
+ "%1 = OpSpecConstantOp %int IAdd %3 %3\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0x7fffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 101\n"
+ "%1 = OpSpecConstantOp %int IAdd %3 %3\n",
+ },
+ // 4. Do not crash and do nothing when SpecId decoration is applied to
+ // multiple spec constants.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %3 %4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n"
+ "%3 = OpSpecConstant %int 200\n"
+ "%4 = OpSpecConstant %int 300\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0xffffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %3 %4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n"
+ "%3 = OpSpecConstant %int 200\n"
+ "%4 = OpSpecConstant %int 300\n",
+ },
+ // 5. Do not crash and do nothing when SpecId decoration is attached to
+ // non-spec-constants (invalid case).
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%2 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_100 = OpConstant %int 100\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0xffffffff"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%2 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_100 = OpConstant %int 100\n",
+ },
+ // 6. Boolean type spec constant cannot be set with numeric values in
+ // string form. i.e. only 'true' and 'false' are acceptable for setting
+ // boolean type spec constants. Nothing should be done if numeric values
+ // in string form are provided.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "OpDecorate %4 SpecId 103\n"
+ "OpDecorate %5 SpecId 104\n"
+ "OpDecorate %6 SpecId 105\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n"
+ "%3 = OpSpecConstantTrue %bool\n"
+ "%4 = OpSpecConstantTrue %bool\n"
+ "%5 = OpSpecConstantTrue %bool\n"
+ "%6 = OpSpecConstantFalse %bool\n",
+ // default values
+ SpecIdToValueStrMap{{100, "0"},
+ {101, "1"},
+ {102, "0x0"},
+ {103, "0.0"},
+ {104, "-0.0"},
+ {105, "0x12345678"}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "OpDecorate %4 SpecId 103\n"
+ "OpDecorate %5 SpecId 104\n"
+ "OpDecorate %6 SpecId 105\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n"
+ "%3 = OpSpecConstantTrue %bool\n"
+ "%4 = OpSpecConstantTrue %bool\n"
+ "%5 = OpSpecConstantTrue %bool\n"
+ "%6 = OpSpecConstantFalse %bool\n",
+ },
+ }));
+
+struct SetSpecConstantDefaultValueInBitPatternFormTestCase {
+ const char* code;
+ SpecIdToValueBitPatternMap default_values;
+ const char* expected;
+};
+
+using SetSpecConstantDefaultValueInBitPatternFormParamTest =
+ PassTest<::testing::TestWithParam<
+ SetSpecConstantDefaultValueInBitPatternFormTestCase>>;
+
+TEST_P(SetSpecConstantDefaultValueInBitPatternFormParamTest, TestCase) {
+ const auto& tc = GetParam();
+ SinglePassRunAndCheck<SetSpecConstantDefaultValuePass>(
+ tc.code, tc.expected, /* skip_nop = */ false, tc.default_values);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ValidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest,
+ ::testing::ValuesIn(std::vector<
+ SetSpecConstantDefaultValueInBitPatternFormTestCase>{
+ // 0. Empty.
+ {"", SpecIdToValueBitPatternMap{}, ""},
+ // 1. Empty with non-empty values to set.
+ {"", SpecIdToValueBitPatternMap{{1, {100}}, {2, {200}}}, ""},
+ // 2. Baisc bool type.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x0}}, {101, {0x1}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantFalse %bool\n"
+ "%2 = OpSpecConstantTrue %bool\n",
+ },
+ // 3. 32-bit int type.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 10\n"
+ "%2 = OpSpecConstant %int 11\n"
+ "%3 = OpSpecConstant %int 11\n",
+ // default values
+ SpecIdToValueBitPatternMap{
+ {100, {2147483647}}, {101, {0xffffffff}}, {102, {0xffffffd6}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 2147483647\n"
+ "%2 = OpSpecConstant %int -1\n"
+ "%3 = OpSpecConstant %int -42\n",
+ },
+ // 4. 64-bit uint type.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %ulong 10\n"
+ "%2 = OpSpecConstant %ulong 11\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0xFFFFFFFE, 0xFFFFFFFF}},
+ {101, {0x100, 0x0}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %ulong 18446744073709551614\n"
+ "%2 = OpSpecConstant %ulong 256\n",
+ },
+ // 5. 32-bit float type.
+ {
+ // code
+ "OpDecorate %1 SpecId 101\n"
+ "OpDecorate %2 SpecId 102\n"
+ "%float = OpTypeFloat 32\n"
+ "%1 = OpSpecConstant %float 200\n"
+ "%2 = OpSpecConstant %float 201\n",
+ // default values
+ SpecIdToValueBitPatternMap{{101, {0xffffffff}},
+ {102, {0x40200000}}},
+ // expected
+ "OpDecorate %1 SpecId 101\n"
+ "OpDecorate %2 SpecId 102\n"
+ "%float = OpTypeFloat 32\n"
+ "%1 = OpSpecConstant %float -0x1.fffffep+128\n"
+ "%2 = OpSpecConstant %float 2.5\n",
+ },
+ // 6. 64-bit float type.
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n"
+ "%2 = OpSpecConstant %double 0.14285\n",
+ // default values
+ SpecIdToValueBitPatternMap{{201, {0xffffffff, 0x7fffffff}},
+ {202, {0x00000000, 0xc0404000}}},
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n"
+ "%2 = OpSpecConstant %double -32.5\n",
+ },
+ // 7. SpecId not found, expect no modification.
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n",
+ // default values
+ SpecIdToValueBitPatternMap{{8888, {0x0}}},
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n",
+ },
+ // 8. Multiple types of spec constants.
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "OpDecorate %3 SpecId 203\n"
+ "%bool = OpTypeBool\n"
+ "%int = OpTypeInt 32 1\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 3.14159265358979\n"
+ "%2 = OpSpecConstant %int 1024\n"
+ "%3 = OpSpecConstantTrue %bool\n",
+ // default values
+ SpecIdToValueBitPatternMap{
+ {201, {0xffffffff, 0x7fffffff}},
+ {202, {0x00000800}},
+ {203, {0x0}},
+ },
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "OpDecorate %3 SpecId 203\n"
+ "%bool = OpTypeBool\n"
+ "%int = OpTypeInt 32 1\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %double 0x1.fffffffffffffp+1024\n"
+ "%2 = OpSpecConstant %int 2048\n"
+ "%3 = OpSpecConstantFalse %bool\n",
+ },
+ // 9. Ignore other decorations.
+ {
+ // code
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{4, {0x7fffffff}}},
+ // expected
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 100\n",
+ },
+ // 10. Distinguish from other decorations.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{4, {0x7fffffff}}, {100, {0xffffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%1 = OpSpecConstant %int -1\n",
+ },
+ // 11. Decorate through decoration group.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x7fffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 2147483647\n",
+ },
+ // 12. Ignore other decorations in decoration group.
+ {
+ // code
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{4, {0x7fffffff}}},
+ // expected
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ },
+ // 13. Distinguish from other decorations in decoration group.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x7fffffff}}, {4, {0x00000001}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %1 ArrayStride 4\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 2147483647\n",
+ },
+ // 14. Unchanged bool default value
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x1}}, {101, {0x0}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n",
+ },
+ // 15. Unchanged int default values
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%int = OpTypeInt 32 1\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %int 10\n"
+ "%2 = OpSpecConstant %ulong 11\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {10}}, {101, {11, 0}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "%int = OpTypeInt 32 1\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%1 = OpSpecConstant %int 10\n"
+ "%2 = OpSpecConstant %ulong 11\n",
+ },
+ // 16. Unchanged float default values
+ {
+ // code
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%float = OpTypeFloat 32\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %float 3.25\n"
+ "%2 = OpSpecConstant %double 1.25\n",
+ // default values
+ SpecIdToValueBitPatternMap{{201, {0x40500000}},
+ {202, {0x00000000, 0x3ff40000}}},
+ // expected
+ "OpDecorate %1 SpecId 201\n"
+ "OpDecorate %2 SpecId 202\n"
+ "%float = OpTypeFloat 32\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %float 3.25\n"
+ "%2 = OpSpecConstant %double 1.25\n",
+ },
+ // 17. OpGroupDecorate may have multiple target ids defined by the same
+ // eligible spec constant
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %2 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0xffffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %2 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int -1\n",
+ },
+ // 18. For Boolean type spec constants,if any word in the bit pattern
+ // is not zero, it can be considered as a 'true', otherwise, it can be
+ // considered as a 'false'.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantTrue %bool\n"
+ "%2 = OpSpecConstantFalse %bool\n"
+ "%3 = OpSpecConstantFalse %bool\n",
+ // default values
+ SpecIdToValueBitPatternMap{
+ {100, {0x0, 0x0, 0x0, 0x0}},
+ {101, {0x10101010}},
+ {102, {0x0, 0x0, 0x0, 0x2}},
+ },
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%bool = OpTypeBool\n"
+ "%1 = OpSpecConstantFalse %bool\n"
+ "%2 = OpSpecConstantTrue %bool\n"
+ "%3 = OpSpecConstantTrue %bool\n",
+ },
+ }));
+
+INSTANTIATE_TEST_CASE_P(
+ InvalidCases, SetSpecConstantDefaultValueInBitPatternFormParamTest,
+ ::testing::ValuesIn(std::vector<
+ SetSpecConstantDefaultValueInBitPatternFormTestCase>{
+ // 0. Do not crash when decoration group is not used.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x7fffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 100\n",
+ },
+ // 1. Do not crash when target does not exist.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x7fffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n",
+ },
+ // 2. Do nothing when SpecId decoration is not attached to a
+ // non-spec-constant instruction.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_101 = OpConstant %int 101\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x7fffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_101 = OpConstant %int 101\n",
+ },
+ // 3. Do nothing when SpecId decoration is not attached to a
+ // OpSpecConstant{|True|False} instruction.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 101\n"
+ "%1 = OpSpecConstantOp %int IAdd %3 %3\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0x7fffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%int = OpTypeInt 32 1\n"
+ "%3 = OpSpecConstant %int 101\n"
+ "%1 = OpSpecConstantOp %int IAdd %3 %3\n",
+ },
+ // 4. Do not crash and do nothing when SpecId decoration is applied to
+ // multiple spec constants.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %3 %4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n"
+ "%3 = OpSpecConstant %int 200\n"
+ "%4 = OpSpecConstant %int 300\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0xffffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2 %3 %4\n"
+ "%int = OpTypeInt 32 1\n"
+ "%2 = OpSpecConstant %int 100\n"
+ "%3 = OpSpecConstant %int 200\n"
+ "%4 = OpSpecConstant %int 300\n",
+ },
+ // 5. Do not crash and do nothing when SpecId decoration is attached to
+ // non-spec-constants (invalid case).
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%2 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_100 = OpConstant %int 100\n",
+ // default values
+ SpecIdToValueBitPatternMap{{100, {0xffffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "%1 = OpDecorationGroup\n"
+ "%2 = OpDecorationGroup\n"
+ "OpGroupDecorate %1 %2\n"
+ "%int = OpTypeInt 32 1\n"
+ "%int_100 = OpConstant %int 100\n",
+ },
+ // 6. Incompatible input bit pattern with the type. Nothing should be
+ // done in such a case.
+ {
+ // code
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%int = OpTypeInt 32 1\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %int 100\n"
+ "%2 = OpSpecConstant %ulong 200\n"
+ "%3 = OpSpecConstant %double 3.141592653\n",
+ // default values
+ SpecIdToValueBitPatternMap{
+ {100, {10, 0}}, {101, {11}}, {102, {0xffffffff}}},
+ // expected
+ "OpDecorate %1 SpecId 100\n"
+ "OpDecorate %2 SpecId 101\n"
+ "OpDecorate %3 SpecId 102\n"
+ "%int = OpTypeInt 32 1\n"
+ "%ulong = OpTypeInt 64 0\n"
+ "%double = OpTypeFloat 64\n"
+ "%1 = OpSpecConstant %int 100\n"
+ "%2 = OpSpecConstant %ulong 200\n"
+ "%3 = OpSpecConstant %double 3.141592653\n",
+ },
+ }));
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/simplification_test.cpp b/test/opt/simplification_test.cpp
new file mode 100644
index 0000000..b7d6f18
--- /dev/null
+++ b/test/opt/simplification_test.cpp
@@ -0,0 +1,207 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "source/opt/simplification_pass.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using SimplificationTest = PassTest<::testing::Test>;
+
+TEST_F(SimplificationTest, StraightLineTest) {
+ // Testing that folding rules are combined in simple straight line code.
+ const std::string text = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %i %o
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 430
+ OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+ OpSourceExtension "GL_GOOGLE_include_directive"
+ OpName %main "main"
+ OpName %i "i"
+ OpName %o "o"
+ OpDecorate %i Flat
+ OpDecorate %i Location 0
+ OpDecorate %o Location 0
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v4int = OpTypeVector %int 4
+ %int_0 = OpConstant %int 0
+ %13 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0
+ %int_1 = OpConstant %int 1
+%_ptr_Input_v4int = OpTypePointer Input %v4int
+ %i = OpVariable %_ptr_Input_v4int Input
+%_ptr_Output_int = OpTypePointer Output %int
+ %o = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %8
+ %21 = OpLabel
+ %31 = OpCompositeInsert %v4int %int_1 %13 0
+; CHECK: [[load:%[a-zA-Z_\d]+]] = OpLoad
+ %23 = OpLoad %v4int %i
+ %33 = OpCompositeInsert %v4int %int_0 %23 0
+ %35 = OpCompositeExtract %int %31 0
+; CHECK: [[extract:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 1
+ %37 = OpCompositeExtract %int %33 1
+; CHECK: [[add:%[a-zA-Z_\d]+]] = OpIAdd %int %int_1 [[extract]]
+ %29 = OpIAdd %int %35 %37
+ OpStore %o %29
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<SimplificationPass>(text, false);
+}
+
+TEST_F(SimplificationTest, AcrossBasicBlocks) {
+ // Testing that folding rules are combined across basic blocks.
+ const std::string text = R"(OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %i %o
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 430
+ OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+ OpSourceExtension "GL_GOOGLE_include_directive"
+ OpName %main "main"
+ OpName %i "i"
+ OpName %o "o"
+ OpDecorate %i Flat
+ OpDecorate %i Location 0
+ OpDecorate %o Location 0
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v4int = OpTypeVector %int 4
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4int = OpTypePointer Input %v4int
+ %i = OpVariable %_ptr_Input_v4int Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_int = OpTypePointer Input %int
+ %int_10 = OpConstant %int 10
+ %bool = OpTypeBool
+ %int_1 = OpConstant %int 1
+%_ptr_Output_int = OpTypePointer Output %int
+ %o = OpVariable %_ptr_Output_int Output
+ %main = OpFunction %void None %8
+ %24 = OpLabel
+; CHECK: [[load:%[a-zA-Z_\d]+]] = OpLoad %v4int %i
+ %25 = OpLoad %v4int %i
+ %41 = OpCompositeInsert %v4int %int_0 %25 0
+ %27 = OpAccessChain %_ptr_Input_int %i %uint_0
+ %28 = OpLoad %int %27
+ %29 = OpSGreaterThan %bool %28 %int_10
+ OpSelectionMerge %30 None
+ OpBranchConditional %29 %31 %32
+ %31 = OpLabel
+ %43 = OpCopyObject %v4int %25
+ OpBranch %30
+ %32 = OpLabel
+ %45 = OpCopyObject %v4int %25
+ OpBranch %30
+ %30 = OpLabel
+ %50 = OpPhi %v4int %43 %31 %45 %32
+; CHECK: [[extract1:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 0
+ %47 = OpCompositeExtract %int %50 0
+; CHECK: [[extract2:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 1
+ %49 = OpCompositeExtract %int %41 1
+; CHECK: [[add:%[a-zA-Z_\d]+]] = OpIAdd %int [[extract1]] [[extract2]]
+ %39 = OpIAdd %int %47 %49
+ OpStore %o %39
+ OpReturn
+ OpFunctionEnd
+
+)";
+
+ SinglePassRunAndMatch<SimplificationPass>(text, false);
+}
+
+TEST_F(SimplificationTest, ThroughLoops) {
+ // Testing that folding rules are applied multiple times to instructions
+ // to be able to propagate across loop iterations.
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %o %i
+ 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 %i "i"
+ OpDecorate %o Location 0
+ OpDecorate %i Flat
+ OpDecorate %i Location 0
+ %void = OpTypeVoid
+ %8 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+ %v4int = OpTypeVector %int 4
+ %int_0 = OpConstant %int 0
+; CHECK: [[constant:%[a-zA-Z_\d]+]] = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0
+ %13 = OpConstantComposite %v4int %int_0 %int_0 %int_0 %int_0
+ %bool = OpTypeBool
+%_ptr_Output_int = OpTypePointer Output %int
+ %o = OpVariable %_ptr_Output_int Output
+%_ptr_Input_v4int = OpTypePointer Input %v4int
+ %i = OpVariable %_ptr_Input_v4int Input
+ %68 = OpUndef %v4int
+ %main = OpFunction %void None %8
+ %23 = OpLabel
+; CHECK: [[load:%[a-zA-Z_\d]+]] = OpLoad %v4int %i
+ %load = OpLoad %v4int %i
+ OpBranch %24
+ %24 = OpLabel
+ %67 = OpPhi %v4int %load %23 %64 %26
+; CHECK: OpLoopMerge [[merge_lab:%[a-zA-Z_\d]+]]
+ OpLoopMerge %25 %26 None
+ OpBranch %27
+ %27 = OpLabel
+ %48 = OpCompositeExtract %int %67 0
+ %30 = OpIEqual %bool %48 %int_0
+ OpBranchConditional %30 %31 %25
+ %31 = OpLabel
+ %50 = OpCompositeExtract %int %67 0
+ %54 = OpCompositeExtract %int %67 1
+ %58 = OpCompositeExtract %int %67 2
+ %62 = OpCompositeExtract %int %67 3
+ %64 = OpCompositeConstruct %v4int %50 %54 %58 %62
+ OpBranch %26
+ %26 = OpLabel
+ OpBranch %24
+ %25 = OpLabel
+; CHECK: [[merge_lab]] = OpLabel
+; CHECK: [[extract:%[a-zA-Z_\d]+]] = OpCompositeExtract %int [[load]] 0
+ %66 = OpCompositeExtract %int %67 0
+; CHECK-NEXT: OpStore %o [[extract]]
+ OpStore %o %66
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<SimplificationPass>(text, false);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/strength_reduction_test.cpp b/test/opt/strength_reduction_test.cpp
new file mode 100644
index 0000000..31d0503
--- /dev/null
+++ b/test/opt/strength_reduction_test.cpp
@@ -0,0 +1,438 @@
+// 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 <algorithm>
+#include <cstdarg>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+using StrengthReductionBasicTest = PassTest<::testing::Test>;
+
+// Test to make sure we replace 5*8.
+TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy8) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %main \"main\"",
+ "%void = OpTypeVoid",
+ "%4 = OpTypeFunction %void",
+ "%uint = OpTypeInt 32 0",
+ "%uint_5 = OpConstant %uint 5",
+ "%uint_8 = OpConstant %uint 8",
+ "%main = OpFunction %void None %4",
+ "%8 = OpLabel",
+ "%9 = OpIMul %uint %uint_5 %uint_8",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+ const std::string& output = std::get<0>(result);
+ EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
+ EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_3"));
+}
+
+// TODO(dneto): Add Effcee as required dependency, and make this unconditional.
+// Test to make sure we replace 16*5
+// Also demonstrate use of Effcee matching.
+TEST_F(StrengthReductionBasicTest, BasicReplaceMulBy16) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+; We know disassembly will produce %uint here, but
+; CHECK: %uint = OpTypeInt 32 0
+; CHECK-DAG: [[five:%[a-zA-Z_\d]+]] = OpConstant %uint 5
+
+; We have RE2 regular expressions, so \w matches [_a-zA-Z0-9].
+; This shows the preferred pattern for matching SPIR-V identifiers.
+; (We could have cheated in this case since we know the disassembler will
+; generate the 'nice' name of "%uint_4".
+; CHECK-DAG: [[four:%\w+]] = OpConstant %uint 4
+ %uint = OpTypeInt 32 0
+ %uint_5 = OpConstant %uint 5
+ %uint_16 = OpConstant %uint 16
+ %main = OpFunction %void None %4
+; CHECK: OpLabel
+ %8 = OpLabel
+; CHECK-NEXT: OpShiftLeftLogical %uint [[five]] [[four]]
+; The multiplication disappears.
+; CHECK-NOT: OpIMul
+ %9 = OpIMul %uint %uint_16 %uint_5
+ OpReturn
+; CHECK: OpFunctionEnd
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<StrengthReductionPass>(text, false);
+}
+
+// Test to make sure we replace a multiple of 32 and 4.
+TEST_F(StrengthReductionBasicTest, BasicTwoPowersOf2) {
+ // In this case, we have two powers of 2. Need to make sure we replace only
+ // one of them for the bit shift.
+ // clang-format off
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main"
+ OpName %main "main"
+ %void = OpTypeVoid
+ %4 = OpTypeFunction %void
+ %int = OpTypeInt 32 1
+%int_32 = OpConstant %int 32
+ %int_4 = OpConstant %int 4
+ %main = OpFunction %void None %4
+ %8 = OpLabel
+ %9 = OpIMul %int %int_32 %int_4
+ OpReturn
+ OpFunctionEnd
+)";
+ // clang-format on
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ text, /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+ const std::string& output = std::get<0>(result);
+ EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
+ EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %int %int_4 %uint_5"));
+}
+
+// Test to make sure we don't replace 0*5.
+TEST_F(StrengthReductionBasicTest, BasicDontReplace0) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %main \"main\"",
+ "%void = OpTypeVoid",
+ "%4 = OpTypeFunction %void",
+ "%int = OpTypeInt 32 1",
+ "%int_0 = OpConstant %int 0",
+ "%int_5 = OpConstant %int 5",
+ "%main = OpFunction %void None %4",
+ "%8 = OpLabel",
+ "%9 = OpIMul %int %int_0 %int_5",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// Test to make sure we do not replace a multiple of 5 and 7.
+TEST_F(StrengthReductionBasicTest, BasicNoChange) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %2 \"main\"",
+ "OpName %2 \"main\"",
+ "%3 = OpTypeVoid",
+ "%4 = OpTypeFunction %3",
+ "%5 = OpTypeInt 32 1",
+ "%6 = OpTypeInt 32 0",
+ "%7 = OpConstant %5 5",
+ "%8 = OpConstant %5 7",
+ "%2 = OpFunction %3 None %4",
+ "%9 = OpLabel",
+ "%10 = OpIMul %5 %7 %8",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
+}
+
+// Test to make sure constants and types are reused and not duplicated.
+TEST_F(StrengthReductionBasicTest, NoDuplicateConstantsAndTypes) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %main \"main\"",
+ "%void = OpTypeVoid",
+ "%4 = OpTypeFunction %void",
+ "%uint = OpTypeInt 32 0",
+ "%uint_8 = OpConstant %uint 8",
+ "%uint_3 = OpConstant %uint 3",
+ "%main = OpFunction %void None %4",
+ "%8 = OpLabel",
+ "%9 = OpIMul %uint %uint_8 %uint_3",
+ "OpReturn",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+ const std::string& output = std::get<0>(result);
+ EXPECT_THAT(output,
+ Not(MatchesRegex(".*OpConstant %uint 3.*OpConstant %uint 3.*")));
+ EXPECT_THAT(output, Not(MatchesRegex(".*OpTypeInt 32 0.*OpTypeInt 32 0.*")));
+}
+
+// Test to make sure we generate the constants only once
+TEST_F(StrengthReductionBasicTest, BasicCreateOneConst) {
+ const std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %main \"main\"",
+ "OpName %main \"main\"",
+ "%void = OpTypeVoid",
+ "%4 = OpTypeFunction %void",
+ "%uint = OpTypeInt 32 0",
+ "%uint_5 = OpConstant %uint 5",
+ "%uint_9 = OpConstant %uint 9",
+ "%uint_128 = OpConstant %uint 128",
+ "%main = OpFunction %void None %4",
+ "%8 = OpLabel",
+ "%9 = OpIMul %uint %uint_5 %uint_128",
+ "%10 = OpIMul %uint %uint_9 %uint_128",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ auto result = SinglePassRunAndDisassemble<StrengthReductionPass>(
+ JoinAllInsts(text), /* skip_nop = */ true, /* do_validation = */ false);
+
+ EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result));
+ const std::string& output = std::get<0>(result);
+ EXPECT_THAT(output, Not(HasSubstr("OpIMul")));
+ EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_5 %uint_7"));
+ EXPECT_THAT(output, HasSubstr("OpShiftLeftLogical %uint %uint_9 %uint_7"));
+}
+
+// Test to make sure we generate the instructions in the correct position and
+// that the uses get replaced as well. Here we check that the use in the return
+// is replaced, we also check that we can replace two OpIMuls when one feeds the
+// other.
+TEST_F(StrengthReductionBasicTest, BasicCheckPositionAndReplacement) {
+ // This is just the preamble to set up the test.
+ const std::vector<const char*> common_text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\" %gl_FragColor",
+ "OpExecutionMode %main OriginUpperLeft",
+ "OpName %main \"main\"",
+ "OpName %foo_i1_ \"foo(i1;\"",
+ "OpName %n \"n\"",
+ "OpName %gl_FragColor \"gl_FragColor\"",
+ "OpName %param \"param\"",
+ "OpDecorate %gl_FragColor Location 0",
+ "%void = OpTypeVoid",
+ "%3 = OpTypeFunction %void",
+ "%int = OpTypeInt 32 1",
+"%_ptr_Function_int = OpTypePointer Function %int",
+ "%8 = OpTypeFunction %int %_ptr_Function_int",
+ "%int_256 = OpConstant %int 256",
+ "%int_2 = OpConstant %int 2",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+ "%float_1 = OpConstant %float 1",
+ "%int_10 = OpConstant %int 10",
+ "%float_0_375 = OpConstant %float 0.375",
+ "%float_0_75 = OpConstant %float 0.75",
+ "%uint = OpTypeInt 32 0",
+ "%uint_8 = OpConstant %uint 8",
+ "%uint_1 = OpConstant %uint 1",
+ "%main = OpFunction %void None %3",
+ "%5 = OpLabel",
+ "%param = OpVariable %_ptr_Function_int Function",
+ "OpStore %param %int_10",
+ "%26 = OpFunctionCall %int %foo_i1_ %param",
+ "%27 = OpConvertSToF %float %26",
+ "%28 = OpFDiv %float %float_1 %27",
+ "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1",
+ "OpStore %gl_FragColor %31",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ // This is the real test. The two OpIMul should be replaced. The expected
+ // output is in |foo_after|.
+ const std::vector<const char*> foo_before = {
+ // clang-format off
+ "%foo_i1_ = OpFunction %int None %8",
+ "%n = OpFunctionParameter %_ptr_Function_int",
+ "%11 = OpLabel",
+ "%12 = OpLoad %int %n",
+ "%14 = OpIMul %int %12 %int_256",
+ "%16 = OpIMul %int %14 %int_2",
+ "OpReturnValue %16",
+ "OpFunctionEnd",
+
+ // clang-format on
+ };
+
+ const std::vector<const char*> foo_after = {
+ // clang-format off
+ "%foo_i1_ = OpFunction %int None %8",
+ "%n = OpFunctionParameter %_ptr_Function_int",
+ "%11 = OpLabel",
+ "%12 = OpLoad %int %n",
+ "%33 = OpShiftLeftLogical %int %12 %uint_8",
+ "%34 = OpShiftLeftLogical %int %33 %uint_1",
+ "OpReturnValue %34",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<StrengthReductionPass>(
+ JoinAllInsts(Concat(common_text, foo_before)),
+ JoinAllInsts(Concat(common_text, foo_after)),
+ /* skip_nop = */ true, /* do_validate = */ true);
+}
+
+// Test that, when the result of an OpIMul instruction has more than 1 use, and
+// the instruction is replaced, all of the uses of the results are replace with
+// the new result.
+TEST_F(StrengthReductionBasicTest, BasicTestMultipleReplacements) {
+ // This is just the preamble to set up the test.
+ const std::vector<const char*> common_text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Fragment %main \"main\" %gl_FragColor",
+ "OpExecutionMode %main OriginUpperLeft",
+ "OpName %main \"main\"",
+ "OpName %foo_i1_ \"foo(i1;\"",
+ "OpName %n \"n\"",
+ "OpName %gl_FragColor \"gl_FragColor\"",
+ "OpName %param \"param\"",
+ "OpDecorate %gl_FragColor Location 0",
+ "%void = OpTypeVoid",
+ "%3 = OpTypeFunction %void",
+ "%int = OpTypeInt 32 1",
+"%_ptr_Function_int = OpTypePointer Function %int",
+ "%8 = OpTypeFunction %int %_ptr_Function_int",
+ "%int_256 = OpConstant %int 256",
+ "%int_2 = OpConstant %int 2",
+ "%float = OpTypeFloat 32",
+ "%v4float = OpTypeVector %float 4",
+"%_ptr_Output_v4float = OpTypePointer Output %v4float",
+"%gl_FragColor = OpVariable %_ptr_Output_v4float Output",
+ "%float_1 = OpConstant %float 1",
+ "%int_10 = OpConstant %int 10",
+ "%float_0_375 = OpConstant %float 0.375",
+ "%float_0_75 = OpConstant %float 0.75",
+ "%uint = OpTypeInt 32 0",
+ "%uint_8 = OpConstant %uint 8",
+ "%uint_1 = OpConstant %uint 1",
+ "%main = OpFunction %void None %3",
+ "%5 = OpLabel",
+ "%param = OpVariable %_ptr_Function_int Function",
+ "OpStore %param %int_10",
+ "%26 = OpFunctionCall %int %foo_i1_ %param",
+ "%27 = OpConvertSToF %float %26",
+ "%28 = OpFDiv %float %float_1 %27",
+ "%31 = OpCompositeConstruct %v4float %28 %float_0_375 %float_0_75 %float_1",
+ "OpStore %gl_FragColor %31",
+ "OpReturn",
+ "OpFunctionEnd"
+ // clang-format on
+ };
+
+ // This is the real test. The two OpIMul instructions should be replaced. In
+ // particular, we want to be sure that both uses of %16 are changed to use the
+ // new result.
+ const std::vector<const char*> foo_before = {
+ // clang-format off
+ "%foo_i1_ = OpFunction %int None %8",
+ "%n = OpFunctionParameter %_ptr_Function_int",
+ "%11 = OpLabel",
+ "%12 = OpLoad %int %n",
+ "%14 = OpIMul %int %12 %int_256",
+ "%16 = OpIMul %int %14 %int_2",
+ "%17 = OpIAdd %int %14 %16",
+ "OpReturnValue %17",
+ "OpFunctionEnd",
+
+ // clang-format on
+ };
+
+ const std::vector<const char*> foo_after = {
+ // clang-format off
+ "%foo_i1_ = OpFunction %int None %8",
+ "%n = OpFunctionParameter %_ptr_Function_int",
+ "%11 = OpLabel",
+ "%12 = OpLoad %int %n",
+ "%34 = OpShiftLeftLogical %int %12 %uint_8",
+ "%35 = OpShiftLeftLogical %int %34 %uint_1",
+ "%17 = OpIAdd %int %34 %35",
+ "OpReturnValue %17",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<StrengthReductionPass>(
+ JoinAllInsts(Concat(common_text, foo_before)),
+ JoinAllInsts(Concat(common_text, foo_after)),
+ /* skip_nop = */ true, /* do_validate = */ true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/strip_debug_info_test.cpp b/test/opt/strip_debug_info_test.cpp
new file mode 100644
index 0000000..f40ed38
--- /dev/null
+++ b/test/opt/strip_debug_info_test.cpp
@@ -0,0 +1,107 @@
+// Copyright (c) 2016 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 <vector>
+
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using StripLineDebugInfoTest = PassTest<::testing::Test>;
+
+TEST_F(StripLineDebugInfoTest, LineNoLine) {
+ std::vector<const char*> text = {
+ // clang-format off
+ "OpCapability Shader",
+ "%1 = OpExtInstImport \"GLSL.std.450\"",
+ "OpMemoryModel Logical GLSL450",
+ "OpEntryPoint Vertex %2 \"main\"",
+ "%3 = OpString \"minimal.vert\"",
+ "OpModuleProcessed \"42\"",
+ "OpModuleProcessed \"43\"",
+ "OpModuleProcessed \"44\"",
+ "OpNoLine",
+ "OpLine %3 10 10",
+ "%void = OpTypeVoid",
+ "OpLine %3 100 100",
+ "%5 = OpTypeFunction %void",
+ "%2 = OpFunction %void None %5",
+ "OpLine %3 1 1",
+ "OpNoLine",
+ "OpLine %3 2 2",
+ "OpLine %3 3 3",
+ "%6 = OpLabel",
+ "OpLine %3 4 4",
+ "OpNoLine",
+ "OpReturn",
+ "OpLine %3 4 4",
+ "OpNoLine",
+ "OpFunctionEnd",
+ // clang-format on
+ };
+ SinglePassRunAndCheck<StripDebugInfoPass>(JoinAllInsts(text),
+ JoinNonDebugInsts(text),
+ /* skip_nop = */ false);
+
+ // Let's add more debug instruction before the "OpString" instruction.
+ const std::vector<const char*> more_text = {
+ "OpSourceContinued \"I'm a happy shader! Yay! ;)\"",
+ "OpSourceContinued \"wahahaha\"",
+ "OpSource ESSL 310",
+ "OpSource ESSL 310",
+ "OpSourceContinued \"wahahaha\"",
+ "OpSourceContinued \"wahahaha\"",
+ "OpSourceExtension \"save-the-world-extension\"",
+ "OpName %2 \"main\"",
+ };
+ text.insert(text.begin() + 4, more_text.cbegin(), more_text.cend());
+ SinglePassRunAndCheck<StripDebugInfoPass>(JoinAllInsts(text),
+ JoinNonDebugInsts(text),
+ /* skip_nop = */ false);
+}
+
+using StripDebugInfoTest = PassTest<::testing::TestWithParam<const char*>>;
+
+TEST_P(StripDebugInfoTest, Kind) {
+ std::vector<const char*> text = {
+ "OpCapability Shader",
+ "OpMemoryModel Logical GLSL450",
+ GetParam(),
+ };
+ SinglePassRunAndCheck<StripDebugInfoPass>(JoinAllInsts(text),
+ JoinNonDebugInsts(text),
+ /* skip_nop = */ false);
+}
+
+// Test each possible non-line debug instruction.
+// clang-format off
+INSTANTIATE_TEST_CASE_P(
+ SingleKindDebugInst, StripDebugInfoTest,
+ ::testing::ValuesIn(std::vector<const char*>({
+ "OpSourceContinued \"I'm a happy shader! Yay! ;)\"",
+ "OpSource ESSL 310",
+ "OpSourceExtension \"save-the-world-extension\"",
+ "OpName %main \"main\"",
+ "OpMemberName %struct 0 \"field\"",
+ "%1 = OpString \"name.vert\"",
+ "OpModuleProcessed \"42\"",
+ })));
+// clang-format on
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/strip_reflect_info_test.cpp b/test/opt/strip_reflect_info_test.cpp
new file mode 100644
index 0000000..a9cfd47
--- /dev/null
+++ b/test/opt/strip_reflect_info_test.cpp
@@ -0,0 +1,90 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 StripLineReflectInfoTest = PassTest<::testing::Test>;
+
+TEST_F(StripLineReflectInfoTest, StripHlslSemantic) {
+ // This is a non-sensical example, but exercises the instructions.
+ std::string before = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_decorate_string"
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpMemoryModel Logical Simple
+OpDecorateStringGOOGLE %float HlslSemanticGOOGLE "foobar"
+OpDecorateStringGOOGLE %void HlslSemanticGOOGLE "my goodness"
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+)";
+ std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical Simple
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+)";
+
+ SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false);
+}
+
+TEST_F(StripLineReflectInfoTest, StripHlslCounterBuffer) {
+ std::string before = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpMemoryModel Logical Simple
+OpDecorateId %void HlslCounterBufferGOOGLE %float
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+)";
+ std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical Simple
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+)";
+
+ SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false);
+}
+
+TEST_F(StripLineReflectInfoTest, StripHlslSemanticOnMember) {
+ // This is a non-sensical example, but exercises the instructions.
+ std::string before = R"(OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_GOOGLE_decorate_string"
+OpExtension "SPV_GOOGLE_hlsl_functionality1"
+OpMemoryModel Logical Simple
+OpMemberDecorateStringGOOGLE %struct 0 HlslSemanticGOOGLE "foobar"
+%float = OpTypeFloat 32
+%_struct_3 = OpTypeStruct %float
+)";
+ std::string after = R"(OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical Simple
+%float = OpTypeFloat 32
+%_struct_3 = OpTypeStruct %float
+)";
+
+ SinglePassRunAndCheck<StripReflectInfoPass>(before, after, false);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/struct_cfg_analysis_test.cpp b/test/opt/struct_cfg_analysis_test.cpp
new file mode 100644
index 0000000..13f9022
--- /dev/null
+++ b/test/opt/struct_cfg_analysis_test.cpp
@@ -0,0 +1,466 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "source/opt/struct_cfg_analysis.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using StructCFGAnalysisTest = PassTest<::testing::Test>;
+
+TEST_F(StructCFGAnalysisTest, BBInSelection) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ StructuredCFGAnalysis analysis(context.get());
+
+ // The header is not in the construct.
+ EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+ EXPECT_EQ(analysis.ContainingLoop(1), 0);
+ EXPECT_EQ(analysis.MergeBlock(1), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+
+ // BB2 is in the construct.
+ EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+ EXPECT_EQ(analysis.ContainingLoop(2), 0);
+ EXPECT_EQ(analysis.MergeBlock(2), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+
+ // The merge node is not in the construct.
+ EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+ EXPECT_EQ(analysis.ContainingLoop(3), 0);
+ EXPECT_EQ(analysis.MergeBlock(3), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+}
+
+TEST_F(StructCFGAnalysisTest, BBInLoop) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpBranch %3
+%4 = OpLabel
+OpBranch %1
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ StructuredCFGAnalysis analysis(context.get());
+
+ // The header is not in the construct.
+ EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+ EXPECT_EQ(analysis.ContainingLoop(1), 0);
+ EXPECT_EQ(analysis.MergeBlock(1), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+
+ // BB2 is in the construct.
+ EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+ EXPECT_EQ(analysis.ContainingLoop(2), 1);
+ EXPECT_EQ(analysis.MergeBlock(2), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+
+ // The merge node is not in the construct.
+ EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+ EXPECT_EQ(analysis.ContainingLoop(3), 0);
+ EXPECT_EQ(analysis.MergeBlock(3), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+
+ // The continue block is in the construct.
+ EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+ EXPECT_EQ(analysis.ContainingLoop(4), 1);
+ EXPECT_EQ(analysis.MergeBlock(4), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+}
+
+TEST_F(StructCFGAnalysisTest, SelectionInLoop) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %undef_bool %5 %6
+%5 = OpLabel
+OpBranch %6
+%6 = OpLabel
+OpBranch %3
+%4 = OpLabel
+OpBranch %1
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ StructuredCFGAnalysis analysis(context.get());
+
+ // The loop header is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+ EXPECT_EQ(analysis.ContainingLoop(1), 0);
+ EXPECT_EQ(analysis.MergeBlock(1), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+
+ // Selection header is in the loop only.
+ EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+ EXPECT_EQ(analysis.ContainingLoop(2), 1);
+ EXPECT_EQ(analysis.MergeBlock(2), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+
+ // The loop merge node is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+ EXPECT_EQ(analysis.ContainingLoop(3), 0);
+ EXPECT_EQ(analysis.MergeBlock(3), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+
+ // The continue block is in the loop only.
+ EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+ EXPECT_EQ(analysis.ContainingLoop(4), 1);
+ EXPECT_EQ(analysis.MergeBlock(4), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+
+ // BB5 is in the selection fist and the loop.
+ EXPECT_EQ(analysis.ContainingConstruct(5), 2);
+ EXPECT_EQ(analysis.ContainingLoop(5), 1);
+ EXPECT_EQ(analysis.MergeBlock(5), 6);
+ EXPECT_EQ(analysis.LoopMergeBlock(5), 3);
+
+ // The selection merge is in the loop only.
+ EXPECT_EQ(analysis.ContainingConstruct(6), 1);
+ EXPECT_EQ(analysis.ContainingLoop(6), 1);
+ EXPECT_EQ(analysis.MergeBlock(6), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(6), 3);
+}
+
+TEST_F(StructCFGAnalysisTest, LoopInSelection) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpLoopMerge %4 %5 None
+OpBranchConditional %undef_bool %4 %6
+%5 = OpLabel
+OpBranch %2
+%6 = OpLabel
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ StructuredCFGAnalysis analysis(context.get());
+
+ // The selection header is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+ EXPECT_EQ(analysis.ContainingLoop(1), 0);
+ EXPECT_EQ(analysis.MergeBlock(1), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+
+ // Loop header is in the selection only.
+ EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+ EXPECT_EQ(analysis.ContainingLoop(2), 0);
+ EXPECT_EQ(analysis.MergeBlock(2), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+
+ // The selection merge node is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+ EXPECT_EQ(analysis.ContainingLoop(3), 0);
+ EXPECT_EQ(analysis.MergeBlock(3), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+
+ // The loop merge is in the selection only.
+ EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+ EXPECT_EQ(analysis.ContainingLoop(4), 0);
+ EXPECT_EQ(analysis.MergeBlock(4), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+
+ // The loop continue target is in the loop.
+ EXPECT_EQ(analysis.ContainingConstruct(5), 2);
+ EXPECT_EQ(analysis.ContainingLoop(5), 2);
+ EXPECT_EQ(analysis.MergeBlock(5), 4);
+ EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+
+ // BB6 is in the loop.
+ EXPECT_EQ(analysis.ContainingConstruct(6), 2);
+ EXPECT_EQ(analysis.ContainingLoop(6), 2);
+ EXPECT_EQ(analysis.MergeBlock(6), 4);
+ EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+}
+
+TEST_F(StructCFGAnalysisTest, SelectionInSelection) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpSelectionMerge %3 None
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpSelectionMerge %4 None
+OpBranchConditional %undef_bool %4 %5
+%5 = OpLabel
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ StructuredCFGAnalysis analysis(context.get());
+
+ // The outer selection header is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+ EXPECT_EQ(analysis.ContainingLoop(1), 0);
+ EXPECT_EQ(analysis.MergeBlock(1), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+
+ // The inner header is in the outer selection.
+ EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+ EXPECT_EQ(analysis.ContainingLoop(2), 0);
+ EXPECT_EQ(analysis.MergeBlock(2), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(2), 0);
+
+ // The outer merge node is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+ EXPECT_EQ(analysis.ContainingLoop(3), 0);
+ EXPECT_EQ(analysis.MergeBlock(3), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+
+ // The inner merge is in the outer selection.
+ EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+ EXPECT_EQ(analysis.ContainingLoop(4), 0);
+ EXPECT_EQ(analysis.MergeBlock(4), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(4), 0);
+
+ // BB5 is in the inner selection.
+ EXPECT_EQ(analysis.ContainingConstruct(5), 2);
+ EXPECT_EQ(analysis.ContainingLoop(5), 0);
+ EXPECT_EQ(analysis.MergeBlock(5), 4);
+ EXPECT_EQ(analysis.LoopMergeBlock(5), 0);
+}
+
+TEST_F(StructCFGAnalysisTest, LoopInLoop) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%uint = OpTypeInt 32 0
+%uint_undef = OpUndef %uint
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%entry_lab = OpLabel
+OpBranch %1
+%1 = OpLabel
+OpLoopMerge %3 %7 None
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpLoopMerge %4 %5 None
+OpBranchConditional %undef_bool %4 %6
+%5 = OpLabel
+OpBranch %2
+%6 = OpLabel
+OpBranch %4
+%4 = OpLabel
+OpBranch %3
+%7 = OpLabel
+OpBranch %1
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ StructuredCFGAnalysis analysis(context.get());
+
+ // The outer loop header is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(1), 0);
+ EXPECT_EQ(analysis.ContainingLoop(1), 0);
+ EXPECT_EQ(analysis.MergeBlock(1), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(1), 0);
+
+ // The inner loop header is in the outer loop.
+ EXPECT_EQ(analysis.ContainingConstruct(2), 1);
+ EXPECT_EQ(analysis.ContainingLoop(2), 1);
+ EXPECT_EQ(analysis.MergeBlock(2), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(2), 3);
+
+ // The outer merge node is not in either construct.
+ EXPECT_EQ(analysis.ContainingConstruct(3), 0);
+ EXPECT_EQ(analysis.ContainingLoop(3), 0);
+ EXPECT_EQ(analysis.MergeBlock(3), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(3), 0);
+
+ // The inner merge is in the outer loop.
+ EXPECT_EQ(analysis.ContainingConstruct(4), 1);
+ EXPECT_EQ(analysis.ContainingLoop(4), 1);
+ EXPECT_EQ(analysis.MergeBlock(4), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(4), 3);
+
+ // The inner continue target is in the inner loop.
+ EXPECT_EQ(analysis.ContainingConstruct(5), 2);
+ EXPECT_EQ(analysis.ContainingLoop(5), 2);
+ EXPECT_EQ(analysis.MergeBlock(5), 4);
+ EXPECT_EQ(analysis.LoopMergeBlock(5), 4);
+
+ // BB6 is in the loop.
+ EXPECT_EQ(analysis.ContainingConstruct(6), 2);
+ EXPECT_EQ(analysis.ContainingLoop(6), 2);
+ EXPECT_EQ(analysis.MergeBlock(6), 4);
+ EXPECT_EQ(analysis.LoopMergeBlock(6), 4);
+
+ // The outer continue target is in the outer loop.
+ EXPECT_EQ(analysis.ContainingConstruct(7), 1);
+ EXPECT_EQ(analysis.ContainingLoop(7), 1);
+ EXPECT_EQ(analysis.MergeBlock(7), 3);
+ EXPECT_EQ(analysis.LoopMergeBlock(7), 3);
+}
+
+TEST_F(StructCFGAnalysisTest, KernelTest) {
+ const std::string text = R"(
+OpCapability Kernel
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%bool_undef = OpUndef %bool
+%void_func = OpTypeFunction %void
+%main = OpFunction %void None %void_func
+%1 = OpLabel
+OpBranchConditional %undef_bool %2 %3
+%2 = OpLabel
+OpBranch %3
+%3 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+
+ StructuredCFGAnalysis analysis(context.get());
+
+ // No structured control flow, so none of the basic block are in any
+ // construct.
+ for (uint32_t i = 1; i <= 3; i++) {
+ EXPECT_EQ(analysis.ContainingConstruct(i), 0);
+ EXPECT_EQ(analysis.ContainingLoop(i), 0);
+ EXPECT_EQ(analysis.MergeBlock(i), 0);
+ EXPECT_EQ(analysis.LoopMergeBlock(i), 0);
+ }
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/type_manager_test.cpp b/test/opt/type_manager_test.cpp
new file mode 100644
index 0000000..1072c36
--- /dev/null
+++ b/test/opt/type_manager_test.cpp
@@ -0,0 +1,1148 @@
+// Copyright (c) 2016 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 <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "effcee/effcee.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/build_module.h"
+#include "source/opt/instruction.h"
+#include "source/opt/type_manager.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+namespace {
+
+bool Validate(const std::vector<uint32_t>& bin) {
+ spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+ spv_context spvContext = spvContextCreate(target_env);
+ spv_diagnostic diagnostic = nullptr;
+ spv_const_binary_t binary = {bin.data(), bin.size()};
+ spv_result_t error = spvValidate(spvContext, &binary, &diagnostic);
+ if (error != 0) spvDiagnosticPrint(diagnostic);
+ spvDiagnosticDestroy(diagnostic);
+ spvContextDestroy(spvContext);
+ return error == 0;
+}
+
+void Match(const std::string& original, IRContext* context,
+ bool do_validation = true) {
+ std::vector<uint32_t> bin;
+ context->module()->ToBinary(&bin, true);
+ if (do_validation) {
+ EXPECT_TRUE(Validate(bin));
+ }
+ std::string assembly;
+ SpirvTools tools(SPV_ENV_UNIVERSAL_1_2);
+ EXPECT_TRUE(
+ tools.Disassemble(bin, &assembly, SpirvTools::kDefaultDisassembleOption))
+ << "Disassembling failed for shader:\n"
+ << assembly << std::endl;
+ auto match_result = effcee::Match(assembly, original);
+ EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+ << match_result.message() << "\nChecking result:\n"
+ << assembly;
+}
+
+std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
+ // Types in this test case are only equal to themselves, nothing else.
+ std::vector<std::unique_ptr<Type>> types;
+
+ // Void, Bool
+ types.emplace_back(new Void());
+ auto* voidt = types.back().get();
+ types.emplace_back(new Bool());
+ auto* boolt = types.back().get();
+
+ // Integer
+ types.emplace_back(new Integer(32, true));
+ auto* s32 = types.back().get();
+ types.emplace_back(new Integer(32, false));
+ types.emplace_back(new Integer(64, true));
+ types.emplace_back(new Integer(64, false));
+ auto* u64 = types.back().get();
+
+ // Float
+ types.emplace_back(new Float(32));
+ auto* f32 = types.back().get();
+ types.emplace_back(new Float(64));
+
+ // Vector
+ types.emplace_back(new Vector(s32, 2));
+ types.emplace_back(new Vector(s32, 3));
+ auto* v3s32 = types.back().get();
+ types.emplace_back(new Vector(u64, 4));
+ types.emplace_back(new Vector(f32, 3));
+ auto* v3f32 = types.back().get();
+
+ // Matrix
+ types.emplace_back(new Matrix(v3s32, 3));
+ types.emplace_back(new Matrix(v3s32, 4));
+ types.emplace_back(new Matrix(v3f32, 4));
+
+ // Images
+ types.emplace_back(new Image(s32, SpvDim2D, 0, 0, 0, 0, SpvImageFormatRg8,
+ SpvAccessQualifierReadOnly));
+ auto* image1 = types.back().get();
+ types.emplace_back(new Image(s32, SpvDim2D, 0, 1, 0, 0, SpvImageFormatRg8,
+ SpvAccessQualifierReadOnly));
+ types.emplace_back(new Image(s32, SpvDim3D, 0, 1, 0, 0, SpvImageFormatRg8,
+ SpvAccessQualifierReadOnly));
+ types.emplace_back(new Image(voidt, SpvDim3D, 0, 1, 0, 1, SpvImageFormatRg8,
+ SpvAccessQualifierReadWrite));
+ auto* image2 = types.back().get();
+
+ // Sampler
+ types.emplace_back(new Sampler());
+
+ // Sampled Image
+ types.emplace_back(new SampledImage(image1));
+ types.emplace_back(new SampledImage(image2));
+
+ // Array
+ types.emplace_back(new Array(f32, 100));
+ types.emplace_back(new Array(f32, 42));
+ auto* a42f32 = types.back().get();
+ types.emplace_back(new Array(u64, 24));
+
+ // RuntimeArray
+ types.emplace_back(new RuntimeArray(v3f32));
+ types.emplace_back(new RuntimeArray(v3s32));
+ auto* rav3s32 = types.back().get();
+
+ // Struct
+ types.emplace_back(new Struct(std::vector<const Type*>{s32}));
+ types.emplace_back(new Struct(std::vector<const Type*>{s32, f32}));
+ auto* sts32f32 = types.back().get();
+ types.emplace_back(
+ new Struct(std::vector<const Type*>{u64, a42f32, rav3s32}));
+
+ // Opaque
+ types.emplace_back(new Opaque(""));
+ types.emplace_back(new Opaque("hello"));
+ types.emplace_back(new Opaque("world"));
+
+ // Pointer
+ types.emplace_back(new Pointer(f32, SpvStorageClassInput));
+ types.emplace_back(new Pointer(sts32f32, SpvStorageClassFunction));
+ types.emplace_back(new Pointer(a42f32, SpvStorageClassFunction));
+
+ // Function
+ types.emplace_back(new Function(voidt, {}));
+ types.emplace_back(new Function(voidt, {boolt}));
+ types.emplace_back(new Function(voidt, {boolt, s32}));
+ types.emplace_back(new Function(s32, {boolt, s32}));
+
+ // Event, Device Event, Reserve Id, Queue,
+ types.emplace_back(new Event());
+ types.emplace_back(new DeviceEvent());
+ types.emplace_back(new ReserveId());
+ types.emplace_back(new Queue());
+
+ // Pipe, Forward Pointer, PipeStorage, NamedBarrier, AccelerationStructureNV
+ types.emplace_back(new Pipe(SpvAccessQualifierReadWrite));
+ types.emplace_back(new Pipe(SpvAccessQualifierReadOnly));
+ types.emplace_back(new ForwardPointer(1, SpvStorageClassInput));
+ types.emplace_back(new ForwardPointer(2, SpvStorageClassInput));
+ types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform));
+ types.emplace_back(new PipeStorage());
+ types.emplace_back(new NamedBarrier());
+ types.emplace_back(new AccelerationStructureNV());
+
+ return types;
+}
+
+TEST(TypeManager, TypeStrings) {
+ const std::string text = R"(
+ OpTypeForwardPointer !20 !2 ; id for %p is 20, Uniform is 2
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %u32 = OpTypeInt 32 0
+ %id4 = OpConstant %u32 4
+ %s32 = OpTypeInt 32 1
+ %f64 = OpTypeFloat 64
+ %v3u32 = OpTypeVector %u32 3
+ %m3x3 = OpTypeMatrix %v3u32 3
+ %img1 = OpTypeImage %s32 Cube 0 1 1 0 R32f ReadWrite
+ %img2 = OpTypeImage %s32 Cube 0 1 1 0 R32f
+ %sampler = OpTypeSampler
+ %si1 = OpTypeSampledImage %img1
+ %si2 = OpTypeSampledImage %img2
+ %a5u32 = OpTypeArray %u32 %id4
+ %af64 = OpTypeRuntimeArray %f64
+ %st1 = OpTypeStruct %u32
+ %st2 = OpTypeStruct %f64 %s32 %v3u32
+ %opaque1 = OpTypeOpaque ""
+ %opaque2 = OpTypeOpaque "opaque"
+ %p = OpTypePointer Uniform %st1
+ %f = OpTypeFunction %void %u32 %u32
+ %event = OpTypeEvent
+ %de = OpTypeDeviceEvent
+ %ri = OpTypeReserveId
+ %queue = OpTypeQueue
+ %pipe = OpTypePipe ReadOnly
+ %ps = OpTypePipeStorage
+ %nb = OpTypeNamedBarrier
+ %rtacc = OpTypeAccelerationStructureNV
+ )";
+
+ std::vector<std::pair<uint32_t, std::string>> type_id_strs = {
+ {1, "void"},
+ {2, "bool"},
+ {3, "uint32"},
+ // Id 4 is used by the constant.
+ {5, "sint32"},
+ {6, "float64"},
+ {7, "<uint32, 3>"},
+ {8, "<<uint32, 3>, 3>"},
+ {9, "image(sint32, 3, 0, 1, 1, 0, 3, 2)"},
+ {10, "image(sint32, 3, 0, 1, 1, 0, 3, 0)"},
+ {11, "sampler"},
+ {12, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 2))"},
+ {13, "sampled_image(image(sint32, 3, 0, 1, 1, 0, 3, 0))"},
+ {14, "[uint32, id(4)]"},
+ {15, "[float64]"},
+ {16, "{uint32}"},
+ {17, "{float64, sint32, <uint32, 3>}"},
+ {18, "opaque('')"},
+ {19, "opaque('opaque')"},
+ {20, "{uint32}*"},
+ {21, "(uint32, uint32) -> void"},
+ {22, "event"},
+ {23, "device_event"},
+ {24, "reserve_id"},
+ {25, "queue"},
+ {26, "pipe(0)"},
+ {27, "pipe_storage"},
+ {28, "named_barrier"},
+ {29, "accelerationStructureNV"},
+ };
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ TypeManager manager(nullptr, context.get());
+
+ EXPECT_EQ(type_id_strs.size(), manager.NumTypes());
+
+ for (const auto& p : type_id_strs) {
+ EXPECT_EQ(p.second, manager.GetType(p.first)->str());
+ EXPECT_EQ(p.first, manager.GetId(manager.GetType(p.first)));
+ }
+}
+
+TEST(TypeManager, StructWithFwdPtr) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ %1 = OpExtInstImport "OpenCL.std"
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %7 "test"
+ OpSource OpenCL_C 102000
+ OpDecorate %11 FuncParamAttr NoCapture
+ %11 = OpDecorationGroup
+ OpGroupDecorate %11 %8 %9
+ OpTypeForwardPointer %100 CrossWorkgroup
+ %void = OpTypeVoid
+ %150 = OpTypeStruct %100
+%100 = OpTypePointer CrossWorkgroup %150
+ %6 = OpTypeFunction %void %100 %100
+ %7 = OpFunction %void Pure %6
+ %8 = OpFunctionParameter %100
+ %9 = OpFunctionParameter %100
+ %10 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ TypeManager manager(nullptr, context.get());
+
+ Type* p100 = manager.GetType(100);
+ Type* s150 = manager.GetType(150);
+
+ EXPECT_TRUE(p100->AsPointer());
+ EXPECT_EQ(p100->AsPointer()->pointee_type(), s150);
+
+ EXPECT_TRUE(s150->AsStruct());
+ EXPECT_EQ(s150->AsStruct()->element_types()[0], p100);
+}
+
+TEST(TypeManager, CircularFwdPtr) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ %1 = OpExtInstImport "OpenCL.std"
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %7 "test"
+ OpSource OpenCL_C 102000
+ OpDecorate %11 FuncParamAttr NoCapture
+ %11 = OpDecorationGroup
+ OpGroupDecorate %11 %8 %9
+ OpTypeForwardPointer %100 CrossWorkgroup
+ OpTypeForwardPointer %200 CrossWorkgroup
+ %void = OpTypeVoid
+ %int = OpTypeInt 32 0
+ %float = OpTypeFloat 32
+ %150 = OpTypeStruct %200 %int
+ %250 = OpTypeStruct %100 %float
+%100 = OpTypePointer CrossWorkgroup %150
+%200 = OpTypePointer CrossWorkgroup %250
+ %6 = OpTypeFunction %void %100 %200
+ %7 = OpFunction %void Pure %6
+ %8 = OpFunctionParameter %100
+ %9 = OpFunctionParameter %200
+ %10 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ TypeManager manager(nullptr, context.get());
+
+ Type* p100 = manager.GetType(100);
+ Type* s150 = manager.GetType(150);
+ Type* p200 = manager.GetType(200);
+ Type* s250 = manager.GetType(250);
+
+ EXPECT_TRUE(p100->AsPointer());
+ EXPECT_EQ(p100->AsPointer()->pointee_type(), s150);
+
+ EXPECT_TRUE(p200->AsPointer());
+ EXPECT_EQ(p200->AsPointer()->pointee_type(), s250);
+
+ EXPECT_TRUE(s150->AsStruct());
+ EXPECT_EQ(s150->AsStruct()->element_types()[0], p200);
+
+ EXPECT_TRUE(s250->AsStruct());
+ EXPECT_EQ(s250->AsStruct()->element_types()[0], p100);
+}
+
+TEST(TypeManager, IsomorphicStructWithFwdPtr) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ %1 = OpExtInstImport "OpenCL.std"
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %7 "test"
+ OpSource OpenCL_C 102000
+ OpDecorate %11 FuncParamAttr NoCapture
+ %11 = OpDecorationGroup
+ OpGroupDecorate %11 %8 %9
+ OpTypeForwardPointer %100 CrossWorkgroup
+ OpTypeForwardPointer %200 CrossWorkgroup
+ %void = OpTypeVoid
+ %_struct_1 = OpTypeStruct %100
+ %_struct_2 = OpTypeStruct %200
+%100 = OpTypePointer CrossWorkgroup %_struct_1
+%200 = OpTypePointer CrossWorkgroup %_struct_2
+ %6 = OpTypeFunction %void %100 %200
+ %7 = OpFunction %void Pure %6
+ %8 = OpFunctionParameter %100
+ %9 = OpFunctionParameter %200
+ %10 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ TypeManager manager(nullptr, context.get());
+
+ EXPECT_EQ(manager.GetType(100), manager.GetType(200));
+}
+
+TEST(TypeManager, IsomorphicCircularFwdPtr) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ %1 = OpExtInstImport "OpenCL.std"
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %7 "test"
+ OpSource OpenCL_C 102000
+ OpDecorate %11 FuncParamAttr NoCapture
+ %11 = OpDecorationGroup
+ OpGroupDecorate %11 %8 %9
+ OpTypeForwardPointer %100 CrossWorkgroup
+ OpTypeForwardPointer %200 CrossWorkgroup
+ OpTypeForwardPointer %300 CrossWorkgroup
+ OpTypeForwardPointer %400 CrossWorkgroup
+ %void = OpTypeVoid
+ %int = OpTypeInt 32 0
+ %float = OpTypeFloat 32
+ %150 = OpTypeStruct %200 %int
+ %250 = OpTypeStruct %100 %float
+ %350 = OpTypeStruct %400 %int
+ %450 = OpTypeStruct %300 %float
+%100 = OpTypePointer CrossWorkgroup %150
+%200 = OpTypePointer CrossWorkgroup %250
+%300 = OpTypePointer CrossWorkgroup %350
+%400 = OpTypePointer CrossWorkgroup %450
+ %6 = OpTypeFunction %void %100 %200
+ %7 = OpFunction %void Pure %6
+ %8 = OpFunctionParameter %100
+ %9 = OpFunctionParameter %200
+ %10 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ TypeManager manager(nullptr, context.get());
+
+ Type* p100 = manager.GetType(100);
+ Type* p300 = manager.GetType(300);
+ EXPECT_EQ(p100, p300);
+ Type* p200 = manager.GetType(200);
+ Type* p400 = manager.GetType(400);
+ EXPECT_EQ(p200, p400);
+
+ Type* p150 = manager.GetType(150);
+ Type* p350 = manager.GetType(350);
+ EXPECT_EQ(p150, p350);
+ Type* p250 = manager.GetType(250);
+ Type* p450 = manager.GetType(450);
+ EXPECT_EQ(p250, p450);
+}
+
+TEST(TypeManager, PartialIsomorphicFwdPtr) {
+ const std::string text = R"(
+ OpCapability Addresses
+ OpCapability Kernel
+ %1 = OpExtInstImport "OpenCL.std"
+ OpMemoryModel Physical64 OpenCL
+ OpEntryPoint Kernel %7 "test"
+ OpSource OpenCL_C 102000
+ OpDecorate %11 FuncParamAttr NoCapture
+ %11 = OpDecorationGroup
+ OpGroupDecorate %11 %8 %9
+ OpTypeForwardPointer %100 CrossWorkgroup
+ OpTypeForwardPointer %200 CrossWorkgroup
+ %void = OpTypeVoid
+ %int = OpTypeInt 32 0
+ %float = OpTypeFloat 32
+ %150 = OpTypeStruct %200 %int
+ %250 = OpTypeStruct %200 %int
+%100 = OpTypePointer CrossWorkgroup %150
+%200 = OpTypePointer CrossWorkgroup %250
+ %6 = OpTypeFunction %void %100 %200
+ %7 = OpFunction %void Pure %6
+ %8 = OpFunctionParameter %100
+ %9 = OpFunctionParameter %200
+ %10 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ TypeManager manager(nullptr, context.get());
+
+ Type* p100 = manager.GetType(100);
+ Type* p200 = manager.GetType(200);
+ EXPECT_EQ(p100->AsPointer()->pointee_type(),
+ p200->AsPointer()->pointee_type());
+}
+
+TEST(TypeManager, DecorationOnStruct) {
+ const std::string text = R"(
+ OpDecorate %struct1 Block
+ OpDecorate %struct2 Block
+ OpDecorate %struct3 Block
+ OpDecorate %struct4 Block
+
+ %u32 = OpTypeInt 32 0 ; id: 5
+ %f32 = OpTypeFloat 32 ; id: 6
+ %struct1 = OpTypeStruct %u32 %f32 ; base
+ %struct2 = OpTypeStruct %f32 %u32 ; different member order
+ %struct3 = OpTypeStruct %f32 ; different member list
+ %struct4 = OpTypeStruct %u32 %f32 ; the same
+ %struct7 = OpTypeStruct %f32 ; no decoration
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ TypeManager manager(nullptr, context.get());
+
+ ASSERT_EQ(7u, manager.NumTypes());
+ // Make sure we get ids correct.
+ ASSERT_EQ("uint32", manager.GetType(5)->str());
+ ASSERT_EQ("float32", manager.GetType(6)->str());
+
+ // Try all combinations of pairs. Expect to be the same type only when the
+ // same id or (1, 4).
+ for (const auto id1 : {1, 2, 3, 4, 7}) {
+ for (const auto id2 : {1, 2, 3, 4, 7}) {
+ if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) {
+ EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+ << "%struct" << id1 << " is expected to be the same as %struct"
+ << id2;
+ } else {
+ EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+ << "%struct" << id1 << " is expected to be different with %struct"
+ << id2;
+ }
+ }
+ }
+}
+
+TEST(TypeManager, DecorationOnMember) {
+ const std::string text = R"(
+ OpMemberDecorate %struct1 0 Offset 0
+ OpMemberDecorate %struct2 0 Offset 0
+ OpMemberDecorate %struct3 0 Offset 0
+ OpMemberDecorate %struct4 0 Offset 0
+ OpMemberDecorate %struct5 1 Offset 0
+ OpMemberDecorate %struct6 0 Offset 4
+
+ OpDecorate %struct7 Block
+ OpMemberDecorate %struct7 0 Offset 0
+
+ %u32 = OpTypeInt 32 0 ; id: 8
+ %f32 = OpTypeFloat 32 ; id: 9
+ %struct1 = OpTypeStruct %u32 %f32 ; base
+ %struct2 = OpTypeStruct %f32 %u32 ; different member order
+ %struct3 = OpTypeStruct %f32 ; different member list
+ %struct4 = OpTypeStruct %u32 %f32 ; the same
+ %struct5 = OpTypeStruct %u32 %f32 ; member decorate different field
+ %struct6 = OpTypeStruct %u32 %f32 ; different member decoration parameter
+ %struct7 = OpTypeStruct %u32 %f32 ; extra decoration on the struct
+ %struct10 = OpTypeStruct %u32 %f32 ; no member decoration
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ TypeManager manager(nullptr, context.get());
+
+ ASSERT_EQ(10u, manager.NumTypes());
+ // Make sure we get ids correct.
+ ASSERT_EQ("uint32", manager.GetType(8)->str());
+ ASSERT_EQ("float32", manager.GetType(9)->str());
+
+ // Try all combinations of pairs. Expect to be the same type only when the
+ // same id or (1, 4).
+ for (const auto id1 : {1, 2, 3, 4, 5, 6, 7, 10}) {
+ for (const auto id2 : {1, 2, 3, 4, 5, 6, 7, 10}) {
+ if (id1 == id2 || (id1 == 1 && id2 == 4) || (id1 == 4 && id2 == 1)) {
+ EXPECT_TRUE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+ << "%struct" << id1 << " is expected to be the same as %struct"
+ << id2;
+ } else {
+ EXPECT_FALSE(manager.GetType(id1)->IsSame(manager.GetType(id2)))
+ << "%struct" << id1 << " is expected to be different with %struct"
+ << id2;
+ }
+ }
+ }
+}
+
+TEST(TypeManager, DecorationEmpty) {
+ const std::string text = R"(
+ OpDecorate %struct1 Block
+ OpMemberDecorate %struct2 0 Offset 0
+
+ %u32 = OpTypeInt 32 0 ; id: 3
+ %f32 = OpTypeFloat 32 ; id: 4
+ %struct1 = OpTypeStruct %u32 %f32
+ %struct2 = OpTypeStruct %f32 %u32
+ %struct5 = OpTypeStruct %f32
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ TypeManager manager(nullptr, context.get());
+
+ ASSERT_EQ(5u, manager.NumTypes());
+ // Make sure we get ids correct.
+ ASSERT_EQ("uint32", manager.GetType(3)->str());
+ ASSERT_EQ("float32", manager.GetType(4)->str());
+
+ // %struct1 with decoration on itself
+ EXPECT_FALSE(manager.GetType(1)->decoration_empty());
+ // %struct2 with decoration on its member
+ EXPECT_FALSE(manager.GetType(2)->decoration_empty());
+ EXPECT_TRUE(manager.GetType(3)->decoration_empty());
+ EXPECT_TRUE(manager.GetType(4)->decoration_empty());
+ // %struct5 has no decorations
+ EXPECT_TRUE(manager.GetType(5)->decoration_empty());
+}
+
+TEST(TypeManager, BeginEndForEmptyModule) {
+ const std::string text = "";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ TypeManager manager(nullptr, context.get());
+ ASSERT_EQ(0u, manager.NumTypes());
+
+ EXPECT_EQ(manager.begin(), manager.end());
+}
+
+TEST(TypeManager, BeginEnd) {
+ const std::string text = R"(
+ %void1 = OpTypeVoid
+ %void2 = OpTypeVoid
+ %bool = OpTypeBool
+ %u32 = OpTypeInt 32 0
+ %f64 = OpTypeFloat 64
+ )";
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text);
+ TypeManager manager(nullptr, context.get());
+ ASSERT_EQ(5u, manager.NumTypes());
+
+ EXPECT_NE(manager.begin(), manager.end());
+ for (const auto& t : manager) {
+ switch (t.first) {
+ case 1:
+ case 2:
+ EXPECT_EQ("void", t.second->str());
+ break;
+ case 3:
+ EXPECT_EQ("bool", t.second->str());
+ break;
+ case 4:
+ EXPECT_EQ("uint32", t.second->str());
+ break;
+ case 5:
+ EXPECT_EQ("float64", t.second->str());
+ break;
+ default:
+ EXPECT_TRUE(false && "unreachable");
+ break;
+ }
+ }
+}
+
+TEST(TypeManager, LookupType) {
+ const std::string text = R"(
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%int = OpTypeInt 32 1
+%vec2 = OpTypeVector %int 2
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(context, nullptr);
+ TypeManager manager(nullptr, context.get());
+
+ Void voidTy;
+ EXPECT_EQ(manager.GetId(&voidTy), 1u);
+
+ Integer uintTy(32, false);
+ EXPECT_EQ(manager.GetId(&uintTy), 2u);
+
+ Integer intTy(32, true);
+ EXPECT_EQ(manager.GetId(&intTy), 3u);
+
+ Integer intTy2(32, true);
+ Vector vecTy(&intTy2, 2u);
+ EXPECT_EQ(manager.GetId(&vecTy), 4u);
+}
+
+TEST(TypeManager, RemoveId) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeInt 32 1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ context->get_type_mgr()->RemoveId(1u);
+ ASSERT_EQ(context->get_type_mgr()->GetType(1u), nullptr);
+ ASSERT_NE(context->get_type_mgr()->GetType(2u), nullptr);
+
+ context->get_type_mgr()->RemoveId(2u);
+ ASSERT_EQ(context->get_type_mgr()->GetType(1u), nullptr);
+ ASSERT_EQ(context->get_type_mgr()->GetType(2u), nullptr);
+}
+
+TEST(TypeManager, RemoveIdNonDuplicateAmbiguousType) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeStruct %1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ Integer u32(32, false);
+ Struct st({&u32});
+ ASSERT_EQ(context->get_type_mgr()->GetId(&st), 2u);
+ context->get_type_mgr()->RemoveId(2u);
+ ASSERT_EQ(context->get_type_mgr()->GetType(2u), nullptr);
+ ASSERT_EQ(context->get_type_mgr()->GetId(&st), 0u);
+}
+
+TEST(TypeManager, RemoveIdDuplicateAmbiguousType) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeStruct %1
+%3 = OpTypeStruct %1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ Integer u32(32, false);
+ Struct st({&u32});
+ uint32_t id = context->get_type_mgr()->GetId(&st);
+ ASSERT_NE(id, 0u);
+ uint32_t toRemove = id == 2u ? 2u : 3u;
+ uint32_t toStay = id == 2u ? 3u : 2u;
+ context->get_type_mgr()->RemoveId(toRemove);
+ ASSERT_EQ(context->get_type_mgr()->GetType(toRemove), nullptr);
+ ASSERT_EQ(context->get_type_mgr()->GetId(&st), toStay);
+}
+
+TEST(TypeManager, RemoveIdDoesntUnmapOtherTypes) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeStruct %1
+%3 = OpTypeStruct %1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ Integer u32(32, false);
+ Struct st({&u32});
+
+ EXPECT_EQ(1u, context->get_type_mgr()->GetId(&u32));
+ uint32_t id = context->get_type_mgr()->GetId(&st);
+ ASSERT_NE(id, 0u);
+ uint32_t toRemove = id == 2u ? 3u : 2u;
+ uint32_t toStay = id == 2u ? 2u : 3u;
+ context->get_type_mgr()->RemoveId(toRemove);
+ ASSERT_EQ(context->get_type_mgr()->GetType(toRemove), nullptr);
+ ASSERT_EQ(context->get_type_mgr()->GetId(&st), toStay);
+}
+
+TEST(TypeManager, GetTypeAndPointerType) {
+ const std::string text = R"(
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeStruct %1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ Integer u32(32, false);
+ Pointer u32Ptr(&u32, SpvStorageClassFunction);
+ Struct st({&u32});
+ Pointer stPtr(&st, SpvStorageClassInput);
+
+ auto pair = context->get_type_mgr()->GetTypeAndPointerType(
+ 3u, SpvStorageClassFunction);
+ ASSERT_EQ(nullptr, pair.first);
+ ASSERT_EQ(nullptr, pair.second);
+
+ pair = context->get_type_mgr()->GetTypeAndPointerType(
+ 1u, SpvStorageClassFunction);
+ ASSERT_TRUE(pair.first->IsSame(&u32));
+ ASSERT_TRUE(pair.second->IsSame(&u32Ptr));
+
+ pair =
+ context->get_type_mgr()->GetTypeAndPointerType(2u, SpvStorageClassInput);
+ ASSERT_TRUE(pair.first->IsSame(&st));
+ ASSERT_TRUE(pair.second->IsSame(&stPtr));
+}
+
+TEST(TypeManager, DuplicateType) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeInt 32 0
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ const Type* type1 = context->get_type_mgr()->GetType(1u);
+ const Type* type2 = context->get_type_mgr()->GetType(2u);
+ EXPECT_NE(type1, nullptr);
+ EXPECT_NE(type2, nullptr);
+ EXPECT_EQ(*type1, *type2);
+}
+
+TEST(TypeManager, MultipleStructs) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpDecorate %3 Constant
+%1 = OpTypeInt 32 0
+%2 = OpTypeStruct %1
+%3 = OpTypeStruct %1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ const Type* type1 = context->get_type_mgr()->GetType(2u);
+ const Type* type2 = context->get_type_mgr()->GetType(3u);
+ EXPECT_NE(type1, nullptr);
+ EXPECT_NE(type2, nullptr);
+ EXPECT_FALSE(type1->IsSame(type2));
+}
+
+TEST(TypeManager, RemovingIdAvoidsUseAfterFree) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+%2 = OpTypeStruct %1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ Integer u32(32, false);
+ Struct st({&u32});
+ const Type* type = context->get_type_mgr()->GetType(2u);
+ EXPECT_NE(type, nullptr);
+ context->get_type_mgr()->RemoveId(1u);
+ EXPECT_TRUE(type->IsSame(&st));
+}
+
+TEST(TypeManager, RegisterAndRemoveId) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ uint32_t id = 2u;
+ {
+ // Ensure that u32 goes out of scope.
+ Integer u32(32, false);
+ Struct st({&u32});
+ context->get_type_mgr()->RegisterType(id, st);
+ }
+
+ context->get_type_mgr()->RemoveId(id);
+ EXPECT_EQ(nullptr, context->get_type_mgr()->GetType(id));
+}
+
+TEST(TypeManager, RegisterAndRemoveIdAllTypes) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
+ uint32_t id = 1u;
+ for (auto& t : types) {
+ context->get_type_mgr()->RegisterType(id, *t);
+ EXPECT_EQ(*t, *context->get_type_mgr()->GetType(id));
+ }
+ types.clear();
+
+ for (; id > 0; --id) {
+ context->get_type_mgr()->RemoveId(id);
+ EXPECT_EQ(nullptr, context->get_type_mgr()->GetType(id));
+ }
+}
+
+TEST(TypeManager, RegisterAndRemoveIdWithDecorations) {
+ const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%1 = OpTypeInt 32 0
+)";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ uint32_t id = 2u;
+ {
+ Integer u32(32, false);
+ Struct st({&u32, &u32});
+ st.AddDecoration({10});
+ st.AddDecoration({11});
+ st.AddMemberDecoration(0, {{35, 4}});
+ st.AddMemberDecoration(1, {{35, 4}});
+ st.AddMemberDecoration(1, {{36, 5}});
+ context->get_type_mgr()->RegisterType(id, st);
+ EXPECT_EQ(st, *context->get_type_mgr()->GetType(id));
+ }
+
+ context->get_type_mgr()->RemoveId(id);
+ EXPECT_EQ(nullptr, context->get_type_mgr()->GetType(id));
+}
+
+TEST(TypeManager, GetTypeInstructionInt) {
+ const std::string text = R"(
+; CHECK: OpTypeInt 32 0
+; CHECK: OpTypeInt 16 1
+OpCapability Shader
+OpCapability Int16
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(context, nullptr);
+
+ Integer uint_32(32, false);
+ context->get_type_mgr()->GetTypeInstruction(&uint_32);
+
+ Integer int_16(16, true);
+ context->get_type_mgr()->GetTypeInstruction(&int_16);
+
+ Match(text, context.get());
+}
+
+TEST(TypeManager, GetTypeInstructionDuplicateInts) {
+ const std::string text = R"(
+; CHECK: OpTypeInt 32 0
+; CHECK-NOT: OpType
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ EXPECT_NE(context, nullptr);
+
+ Integer uint_32(32, false);
+ uint32_t id = context->get_type_mgr()->GetTypeInstruction(&uint_32);
+
+ Integer other(32, false);
+ EXPECT_EQ(context->get_type_mgr()->GetTypeInstruction(&other), id);
+
+ Match(text, context.get());
+}
+
+TEST(TypeManager, GetTypeInstructionAllTypes) {
+ const std::string text = R"(
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[input_ptr:%\w+]] = OpTypePointer Input [[uint]]
+; CHECK: [[uniform_ptr:%\w+]] = OpTypePointer Uniform [[uint]]
+; CHECK: [[uint24:%\w+]] = OpConstant [[uint]] 24
+; CHECK: [[uint42:%\w+]] = OpConstant [[uint]] 42
+; CHECK: [[uint100:%\w+]] = OpConstant [[uint]] 100
+; CHECK: [[void:%\w+]] = OpTypeVoid
+; CHECK: [[bool:%\w+]] = OpTypeBool
+; CHECK: [[s32:%\w+]] = OpTypeInt 32 1
+; CHECK: OpTypeInt 64 1
+; CHECK: [[u64:%\w+]] = OpTypeInt 64 0
+; CHECK: [[f32:%\w+]] = OpTypeFloat 32
+; CHECK: OpTypeFloat 64
+; CHECK: OpTypeVector [[s32]] 2
+; CHECK: [[v3s32:%\w+]] = OpTypeVector [[s32]] 3
+; CHECK: OpTypeVector [[u64]] 4
+; CHECK: [[v3f32:%\w+]] = OpTypeVector [[f32]] 3
+; CHECK: OpTypeMatrix [[v3s32]] 3
+; CHECK: OpTypeMatrix [[v3s32]] 4
+; CHECK: OpTypeMatrix [[v3f32]] 4
+; CHECK: [[image1:%\w+]] = OpTypeImage [[s32]] 2D 0 0 0 0 Rg8 ReadOnly
+; CHECK: OpTypeImage [[s32]] 2D 0 1 0 0 Rg8 ReadOnly
+; CHECK: OpTypeImage [[s32]] 3D 0 1 0 0 Rg8 ReadOnly
+; CHECK: [[image2:%\w+]] = OpTypeImage [[void]] 3D 0 1 0 1 Rg8 ReadWrite
+; CHECK: OpTypeSampler
+; CHECK: OpTypeSampledImage [[image1]]
+; CHECK: OpTypeSampledImage [[image2]]
+; CHECK: OpTypeArray [[f32]] [[uint100]]
+; CHECK: [[a42f32:%\w+]] = OpTypeArray [[f32]] [[uint42]]
+; CHECK: OpTypeArray [[u64]] [[uint24]]
+; CHECK: OpTypeRuntimeArray [[v3f32]]
+; CHECK: [[rav3s32:%\w+]] = OpTypeRuntimeArray [[v3s32]]
+; CHECK: OpTypeStruct [[s32]]
+; CHECK: [[sts32f32:%\w+]] = OpTypeStruct [[s32]] [[f32]]
+; CHECK: OpTypeStruct [[u64]] [[a42f32]] [[rav3s32]]
+; CHECK: OpTypeOpaque ""
+; CHECK: OpTypeOpaque "hello"
+; CHECK: OpTypeOpaque "world"
+; CHECK: OpTypePointer Input [[f32]]
+; CHECK: OpTypePointer Function [[sts32f32]]
+; CHECK: OpTypePointer Function [[a42f32]]
+; CHECK: OpTypeFunction [[void]]
+; CHECK: OpTypeFunction [[void]] [[bool]]
+; CHECK: OpTypeFunction [[void]] [[bool]] [[s32]]
+; CHECK: OpTypeFunction [[s32]] [[bool]] [[s32]]
+; CHECK: OpTypeEvent
+; CHECK: OpTypeDeviceEvent
+; CHECK: OpTypeReserveId
+; CHECK: OpTypeQueue
+; CHECK: OpTypePipe ReadWrite
+; CHECK: OpTypePipe ReadOnly
+; CHECK: OpTypeForwardPointer [[input_ptr]] Input
+; CHECK: OpTypeForwardPointer [[uniform_ptr]] Input
+; CHECK: OpTypeForwardPointer [[uniform_ptr]] Uniform
+; CHECK: OpTypePipeStorage
+; CHECK: OpTypeNamedBarrier
+; CHECK: OpTypeAccelerationStructureNV
+OpCapability Shader
+OpCapability Int64
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%uint = OpTypeInt 32 0
+%1 = OpTypePointer Input %uint
+%2 = OpTypePointer Uniform %uint
+%24 = OpConstant %uint 24
+%42 = OpConstant %uint 42
+%100 = OpConstant %uint 100
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
+ for (auto& t : types) {
+ context->get_type_mgr()->GetTypeInstruction(t.get());
+ }
+
+ Match(text, context.get(), false);
+}
+
+TEST(TypeManager, GetTypeInstructionWithDecorations) {
+ const std::string text = R"(
+; CHECK: OpDecorate [[struct:%\w+]] CPacked
+; CHECK: OpMemberDecorate [[struct]] 1 Offset 4
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[struct]] = OpTypeStruct [[uint]] [[uint]]
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%uint = OpTypeInt 32 0
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ Integer u32(32, false);
+ Struct st({&u32, &u32});
+ st.AddDecoration({10});
+ st.AddMemberDecoration(1, {{35, 4}});
+ (void)context->get_def_use_mgr();
+ context->get_type_mgr()->GetTypeInstruction(&st);
+
+ Match(text, context.get());
+}
+
+TEST(TypeManager, GetPointerToAmbiguousType1) {
+ const std::string text = R"(
+; CHECK: [[struct1:%\w+]] = OpTypeStruct
+; CHECK: [[struct2:%\w+]] = OpTypeStruct
+; CHECK: OpTypePointer Function [[struct2]]
+; CHECK: OpTypePointer Function [[struct1]]
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%uint = OpTypeInt 32 0
+%1 = OpTypeStruct %uint
+%2 = OpTypeStruct %uint
+%3 = OpTypePointer Function %2
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ context->get_type_mgr()->FindPointerToType(1, SpvStorageClassFunction);
+ Match(text, context.get());
+}
+
+TEST(TypeManager, GetPointerToAmbiguousType2) {
+ const std::string text = R"(
+; CHECK: [[struct1:%\w+]] = OpTypeStruct
+; CHECK: [[struct2:%\w+]] = OpTypeStruct
+; CHECK: OpTypePointer Function [[struct1]]
+; CHECK: OpTypePointer Function [[struct2]]
+OpCapability Shader
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%uint = OpTypeInt 32 0
+%1 = OpTypeStruct %uint
+%2 = OpTypeStruct %uint
+%3 = OpTypePointer Function %1
+ )";
+
+ std::unique_ptr<IRContext> context =
+ BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+ SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ EXPECT_NE(context, nullptr);
+
+ context->get_type_mgr()->FindPointerToType(2, SpvStorageClassFunction);
+ Match(text, context.get());
+}
+
+} // namespace
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/types_test.cpp b/test/opt/types_test.cpp
new file mode 100644
index 0000000..7426ed7
--- /dev/null
+++ b/test/opt/types_test.cpp
@@ -0,0 +1,345 @@
+// Copyright (c) 2016 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 <memory>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "source/opt/types.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+namespace {
+
+// Fixture class providing some element types.
+class SameTypeTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ void_t_ = MakeUnique<Void>();
+ u32_t_ = MakeUnique<Integer>(32, false);
+ f64_t_ = MakeUnique<Float>(64);
+ v3u32_t_ = MakeUnique<Vector>(u32_t_.get(), 3);
+ image_t_ =
+ MakeUnique<Image>(f64_t_.get(), SpvDim2D, 1, 1, 0, 0, SpvImageFormatR16,
+ SpvAccessQualifierReadWrite);
+ }
+
+ // Element types to be used for constructing other types for testing.
+ std::unique_ptr<Type> void_t_;
+ std::unique_ptr<Type> u32_t_;
+ std::unique_ptr<Type> f64_t_;
+ std::unique_ptr<Type> v3u32_t_;
+ std::unique_ptr<Type> image_t_;
+};
+
+#define TestMultipleInstancesOfTheSameType(ty, ...) \
+ TEST_F(SameTypeTest, MultiSame##ty) { \
+ std::vector<std::unique_ptr<Type>> types; \
+ for (int i = 0; i < 10; ++i) types.emplace_back(new ty(__VA_ARGS__)); \
+ for (size_t i = 0; i < types.size(); ++i) { \
+ for (size_t j = 0; j < types.size(); ++j) { \
+ EXPECT_TRUE(types[i]->IsSame(types[j].get())) \
+ << "expected '" << types[i]->str() << "' is the same as '" \
+ << types[j]->str() << "'"; \
+ EXPECT_TRUE(*types[i] == *types[j]) \
+ << "expected '" << types[i]->str() << "' is the same as '" \
+ << types[j]->str() << "'"; \
+ } \
+ } \
+ }
+TestMultipleInstancesOfTheSameType(Void);
+TestMultipleInstancesOfTheSameType(Bool);
+TestMultipleInstancesOfTheSameType(Integer, 32, true);
+TestMultipleInstancesOfTheSameType(Float, 64);
+TestMultipleInstancesOfTheSameType(Vector, u32_t_.get(), 3);
+TestMultipleInstancesOfTheSameType(Matrix, v3u32_t_.get(), 4);
+TestMultipleInstancesOfTheSameType(Image, f64_t_.get(), SpvDimCube, 0, 0, 1, 1,
+ SpvImageFormatRgb10A2,
+ SpvAccessQualifierWriteOnly);
+TestMultipleInstancesOfTheSameType(Sampler);
+TestMultipleInstancesOfTheSameType(SampledImage, image_t_.get());
+TestMultipleInstancesOfTheSameType(Array, u32_t_.get(), 10);
+TestMultipleInstancesOfTheSameType(RuntimeArray, u32_t_.get());
+TestMultipleInstancesOfTheSameType(Struct, std::vector<const Type*>{
+ u32_t_.get(), f64_t_.get()});
+TestMultipleInstancesOfTheSameType(Opaque, "testing rocks");
+TestMultipleInstancesOfTheSameType(Pointer, u32_t_.get(), SpvStorageClassInput);
+TestMultipleInstancesOfTheSameType(Function, u32_t_.get(),
+ {f64_t_.get(), f64_t_.get()});
+TestMultipleInstancesOfTheSameType(Event);
+TestMultipleInstancesOfTheSameType(DeviceEvent);
+TestMultipleInstancesOfTheSameType(ReserveId);
+TestMultipleInstancesOfTheSameType(Queue);
+TestMultipleInstancesOfTheSameType(Pipe, SpvAccessQualifierReadWrite);
+TestMultipleInstancesOfTheSameType(ForwardPointer, 10, SpvStorageClassUniform);
+TestMultipleInstancesOfTheSameType(PipeStorage);
+TestMultipleInstancesOfTheSameType(NamedBarrier);
+TestMultipleInstancesOfTheSameType(AccelerationStructureNV);
+#undef TestMultipleInstanceOfTheSameType
+
+std::vector<std::unique_ptr<Type>> GenerateAllTypes() {
+ // Types in this test case are only equal to themselves, nothing else.
+ std::vector<std::unique_ptr<Type>> types;
+
+ // Forward Pointer
+ types.emplace_back(new ForwardPointer(10000, SpvStorageClassInput));
+ types.emplace_back(new ForwardPointer(20000, SpvStorageClassInput));
+
+ // Void, Bool
+ types.emplace_back(new Void());
+ auto* voidt = types.back().get();
+ types.emplace_back(new Bool());
+ auto* boolt = types.back().get();
+
+ // Integer
+ types.emplace_back(new Integer(32, true));
+ auto* s32 = types.back().get();
+ types.emplace_back(new Integer(32, false));
+ types.emplace_back(new Integer(64, true));
+ types.emplace_back(new Integer(64, false));
+ auto* u64 = types.back().get();
+
+ // Float
+ types.emplace_back(new Float(32));
+ auto* f32 = types.back().get();
+ types.emplace_back(new Float(64));
+
+ // Vector
+ types.emplace_back(new Vector(s32, 2));
+ types.emplace_back(new Vector(s32, 3));
+ auto* v3s32 = types.back().get();
+ types.emplace_back(new Vector(u64, 4));
+ types.emplace_back(new Vector(f32, 3));
+ auto* v3f32 = types.back().get();
+
+ // Matrix
+ types.emplace_back(new Matrix(v3s32, 3));
+ types.emplace_back(new Matrix(v3s32, 4));
+ types.emplace_back(new Matrix(v3f32, 4));
+
+ // Images
+ types.emplace_back(new Image(s32, SpvDim2D, 0, 0, 0, 0, SpvImageFormatRg8,
+ SpvAccessQualifierReadOnly));
+ auto* image1 = types.back().get();
+ types.emplace_back(new Image(s32, SpvDim2D, 0, 1, 0, 0, SpvImageFormatRg8,
+ SpvAccessQualifierReadOnly));
+ types.emplace_back(new Image(s32, SpvDim3D, 0, 1, 0, 0, SpvImageFormatRg8,
+ SpvAccessQualifierReadOnly));
+ types.emplace_back(new Image(voidt, SpvDim3D, 0, 1, 0, 1, SpvImageFormatRg8,
+ SpvAccessQualifierReadWrite));
+ auto* image2 = types.back().get();
+
+ // Sampler
+ types.emplace_back(new Sampler());
+
+ // Sampled Image
+ types.emplace_back(new SampledImage(image1));
+ types.emplace_back(new SampledImage(image2));
+
+ // Array
+ types.emplace_back(new Array(f32, 100));
+ types.emplace_back(new Array(f32, 42));
+ auto* a42f32 = types.back().get();
+ types.emplace_back(new Array(u64, 24));
+
+ // RuntimeArray
+ types.emplace_back(new RuntimeArray(v3f32));
+ types.emplace_back(new RuntimeArray(v3s32));
+ auto* rav3s32 = types.back().get();
+
+ // Struct
+ types.emplace_back(new Struct(std::vector<const Type*>{s32}));
+ types.emplace_back(new Struct(std::vector<const Type*>{s32, f32}));
+ auto* sts32f32 = types.back().get();
+ types.emplace_back(
+ new Struct(std::vector<const Type*>{u64, a42f32, rav3s32}));
+
+ // Opaque
+ types.emplace_back(new Opaque(""));
+ types.emplace_back(new Opaque("hello"));
+ types.emplace_back(new Opaque("world"));
+
+ // Pointer
+ types.emplace_back(new Pointer(f32, SpvStorageClassInput));
+ types.emplace_back(new Pointer(sts32f32, SpvStorageClassFunction));
+ types.emplace_back(new Pointer(a42f32, SpvStorageClassFunction));
+ types.emplace_back(new Pointer(voidt, SpvStorageClassFunction));
+
+ // Function
+ types.emplace_back(new Function(voidt, {}));
+ types.emplace_back(new Function(voidt, {boolt}));
+ types.emplace_back(new Function(voidt, {boolt, s32}));
+ types.emplace_back(new Function(s32, {boolt, s32}));
+
+ // Event, Device Event, Reserve Id, Queue,
+ types.emplace_back(new Event());
+ types.emplace_back(new DeviceEvent());
+ types.emplace_back(new ReserveId());
+ types.emplace_back(new Queue());
+
+ // Pipe, Forward Pointer, PipeStorage, NamedBarrier
+ types.emplace_back(new Pipe(SpvAccessQualifierReadWrite));
+ types.emplace_back(new Pipe(SpvAccessQualifierReadOnly));
+ types.emplace_back(new ForwardPointer(1, SpvStorageClassInput));
+ types.emplace_back(new ForwardPointer(2, SpvStorageClassInput));
+ types.emplace_back(new ForwardPointer(2, SpvStorageClassUniform));
+ types.emplace_back(new PipeStorage());
+ types.emplace_back(new NamedBarrier());
+
+ return types;
+}
+
+TEST(Types, AllTypes) {
+ // Types in this test case are only equal to themselves, nothing else.
+ std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
+
+ for (size_t i = 0; i < types.size(); ++i) {
+ for (size_t j = 0; j < types.size(); ++j) {
+ if (i == j) {
+ EXPECT_TRUE(types[i]->IsSame(types[j].get()))
+ << "expected '" << types[i]->str() << "' is the same as '"
+ << types[j]->str() << "'";
+ } else {
+ EXPECT_FALSE(types[i]->IsSame(types[j].get()))
+ << "expected '" << types[i]->str() << "' is different to '"
+ << types[j]->str() << "'";
+ }
+ }
+ }
+}
+
+TEST(Types, IntSignedness) {
+ std::vector<bool> signednesses = {true, false, false, true};
+ std::vector<std::unique_ptr<Integer>> types;
+ for (bool s : signednesses) {
+ types.emplace_back(new Integer(32, s));
+ }
+ for (size_t i = 0; i < signednesses.size(); i++) {
+ EXPECT_EQ(signednesses[i], types[i]->IsSigned());
+ }
+}
+
+TEST(Types, IntWidth) {
+ std::vector<uint32_t> widths = {1, 2, 4, 8, 16, 32, 48, 64, 128};
+ std::vector<std::unique_ptr<Integer>> types;
+ for (uint32_t w : widths) {
+ types.emplace_back(new Integer(w, true));
+ }
+ for (size_t i = 0; i < widths.size(); i++) {
+ EXPECT_EQ(widths[i], types[i]->width());
+ }
+}
+
+TEST(Types, FloatWidth) {
+ std::vector<uint32_t> widths = {1, 2, 4, 8, 16, 32, 48, 64, 128};
+ std::vector<std::unique_ptr<Float>> types;
+ for (uint32_t w : widths) {
+ types.emplace_back(new Float(w));
+ }
+ for (size_t i = 0; i < widths.size(); i++) {
+ EXPECT_EQ(widths[i], types[i]->width());
+ }
+}
+
+TEST(Types, VectorElementCount) {
+ auto s32 = MakeUnique<Integer>(32, true);
+ for (uint32_t c : {2, 3, 4}) {
+ auto s32v = MakeUnique<Vector>(s32.get(), c);
+ EXPECT_EQ(c, s32v->element_count());
+ }
+}
+
+TEST(Types, MatrixElementCount) {
+ auto s32 = MakeUnique<Integer>(32, true);
+ auto s32v4 = MakeUnique<Vector>(s32.get(), 4);
+ for (uint32_t c : {1, 2, 3, 4, 10, 100}) {
+ auto s32m = MakeUnique<Matrix>(s32v4.get(), c);
+ EXPECT_EQ(c, s32m->element_count());
+ }
+}
+
+TEST(Types, IsUniqueType) {
+ std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
+
+ for (auto& t : types) {
+ bool expectation = true;
+ // Disallowing variable pointers.
+ switch (t->kind()) {
+ case Type::kArray:
+ case Type::kRuntimeArray:
+ case Type::kStruct:
+ expectation = false;
+ break;
+ default:
+ break;
+ }
+ EXPECT_EQ(t->IsUniqueType(false), expectation)
+ << "expected '" << t->str() << "' to be a "
+ << (expectation ? "" : "non-") << "unique type";
+
+ // Allowing variables pointers.
+ if (t->AsPointer()) expectation = false;
+ EXPECT_EQ(t->IsUniqueType(true), expectation)
+ << "expected '" << t->str() << "' to be a "
+ << (expectation ? "" : "non-") << "unique type";
+ }
+}
+
+std::vector<std::unique_ptr<Type>> GenerateAllTypesWithDecorations() {
+ std::vector<std::unique_ptr<Type>> types = GenerateAllTypes();
+ uint32_t elems = 1;
+ uint32_t decs = 1;
+ for (auto& t : types) {
+ for (uint32_t i = 0; i < (decs % 10); ++i) {
+ std::vector<uint32_t> decoration;
+ for (uint32_t j = 0; j < (elems % 4) + 1; ++j) {
+ decoration.push_back(j);
+ }
+ t->AddDecoration(std::move(decoration));
+ ++elems;
+ ++decs;
+ }
+ }
+
+ return types;
+}
+
+TEST(Types, Clone) {
+ std::vector<std::unique_ptr<Type>> types = GenerateAllTypesWithDecorations();
+ for (auto& t : types) {
+ auto clone = t->Clone();
+ EXPECT_TRUE(*t == *clone);
+ EXPECT_TRUE(t->HasSameDecorations(clone.get()));
+ EXPECT_NE(clone.get(), t.get());
+ }
+}
+
+TEST(Types, RemoveDecorations) {
+ std::vector<std::unique_ptr<Type>> types = GenerateAllTypesWithDecorations();
+ for (auto& t : types) {
+ auto decorationless = t->RemoveDecorations();
+ EXPECT_EQ(*t == *decorationless, t->decoration_empty());
+ EXPECT_EQ(t->HasSameDecorations(decorationless.get()),
+ t->decoration_empty());
+ EXPECT_NE(t.get(), decorationless.get());
+ }
+}
+
+} // namespace
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/unify_const_test.cpp b/test/opt/unify_const_test.cpp
new file mode 100644
index 0000000..37728cc
--- /dev/null
+++ b/test/opt/unify_const_test.cpp
@@ -0,0 +1,990 @@
+// Copyright (c) 2016 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 <tuple>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+// Returns the types defining instructions commonly used in many tests.
+std::vector<std::string> CommonTypes() {
+ return std::vector<std::string>{
+ // clang-format off
+ // scalar types
+ "%bool = OpTypeBool",
+ "%uint = OpTypeInt 32 0",
+ "%int = OpTypeInt 32 1",
+ "%uint64 = OpTypeInt 64 0",
+ "%int64 = OpTypeInt 64 1",
+ "%float = OpTypeFloat 32",
+ "%double = OpTypeFloat 64",
+ // vector types
+ "%v2bool = OpTypeVector %bool 2",
+ "%v2uint = OpTypeVector %uint 2",
+ "%v2int = OpTypeVector %int 2",
+ "%v3int = OpTypeVector %int 3",
+ "%v4int = OpTypeVector %int 4",
+ "%v2float = OpTypeVector %float 2",
+ "%v3float = OpTypeVector %float 3",
+ "%v2double = OpTypeVector %double 2",
+ // struct types
+ "%inner_struct = OpTypeStruct %bool %float",
+ "%outer_struct = OpTypeStruct %inner_struct %int %double",
+ "%flat_struct = OpTypeStruct %bool %int %float %double",
+ // variable pointer types
+ "%_pf_bool = OpTypePointer Function %bool",
+ "%_pf_uint = OpTypePointer Function %uint",
+ "%_pf_int = OpTypePointer Function %int",
+ "%_pf_uint64 = OpTypePointer Function %uint64",
+ "%_pf_int64 = OpTypePointer Function %int64",
+ "%_pf_float = OpTypePointer Function %float",
+ "%_pf_double = OpTypePointer Function %double",
+ "%_pf_v2int = OpTypePointer Function %v2int",
+ "%_pf_v3int = OpTypePointer Function %v3int",
+ "%_pf_v4int = OpTypePointer Function %v4int",
+ "%_pf_v2float = OpTypePointer Function %v2float",
+ "%_pf_v3float = OpTypePointer Function %v3float",
+ "%_pf_v2double = OpTypePointer Function %v2double",
+ "%_pf_inner_struct = OpTypePointer Function %inner_struct",
+ "%_pf_outer_struct = OpTypePointer Function %outer_struct",
+ "%_pf_flat_struct = OpTypePointer Function %flat_struct",
+ // clang-format on
+ };
+}
+
+// A helper function to strip OpName instructions from the given string of
+// disassembly code and put those debug instructions to a set. Returns the
+// string with all OpName instruction stripped and a set of OpName
+// instructions.
+std::tuple<std::string, std::unordered_set<std::string>>
+StripOpNameInstructionsToSet(const std::string& str) {
+ std::stringstream ss(str);
+ std::ostringstream oss;
+ std::string inst_str;
+ std::unordered_set<std::string> opname_instructions;
+ while (std::getline(ss, inst_str, '\n')) {
+ if (inst_str.find("OpName %") == std::string::npos) {
+ oss << inst_str << '\n';
+ } else {
+ opname_instructions.insert(inst_str);
+ }
+ }
+ return std::make_tuple(oss.str(), std::move(opname_instructions));
+}
+
+// The test fixture for all tests of UnifyConstantPass. This fixture defines
+// the rule of checking: all the optimized code should be exactly the same as
+// the expected code, except the OpName instructions, which can be different in
+// order.
+template <typename T>
+class UnifyConstantTest : public PassTest<T> {
+ protected:
+ // Runs UnifyConstantPass on the code built from the given |test_builder|,
+ // and checks whether the optimization result matches with the code built
+ // from |expected_builder|.
+ void Check(const AssemblyBuilder& expected_builder,
+ const AssemblyBuilder& test_builder) {
+ // unoptimized code
+ const std::string original_before_strip = test_builder.GetCode();
+ std::string original_without_opnames;
+ std::unordered_set<std::string> original_opnames;
+ std::tie(original_without_opnames, original_opnames) =
+ StripOpNameInstructionsToSet(original_before_strip);
+
+ // expected code
+ std::string expected_without_opnames;
+ std::unordered_set<std::string> expected_opnames;
+ std::tie(expected_without_opnames, expected_opnames) =
+ StripOpNameInstructionsToSet(expected_builder.GetCode());
+
+ // optimized code
+ std::string optimized_before_strip;
+ auto status = Pass::Status::SuccessWithoutChange;
+ std::tie(optimized_before_strip, status) =
+ this->template SinglePassRunAndDisassemble<UnifyConstantPass>(
+ test_builder.GetCode(),
+ /* skip_nop = */ true, /* do_validation = */ false);
+ std::string optimized_without_opnames;
+ std::unordered_set<std::string> optimized_opnames;
+ std::tie(optimized_without_opnames, optimized_opnames) =
+ StripOpNameInstructionsToSet(optimized_before_strip);
+
+ // Flag "status" should be returned correctly.
+ EXPECT_NE(Pass::Status::Failure, status);
+ EXPECT_EQ(expected_without_opnames == original_without_opnames,
+ status == Pass::Status::SuccessWithoutChange);
+ // Code except OpName instructions should be exactly the same.
+ EXPECT_EQ(expected_without_opnames, optimized_without_opnames);
+ // OpName instructions can be in different order, but the content must be
+ // the same.
+ EXPECT_EQ(expected_opnames, optimized_opnames);
+ }
+};
+
+using UnifyFrontEndConstantSingleTest =
+ UnifyConstantTest<PassTest<::testing::Test>>;
+
+TEST_F(UnifyFrontEndConstantSingleTest, Basic) {
+ AssemblyBuilder test_builder;
+ AssemblyBuilder expected_builder;
+
+ test_builder
+ .AppendTypesConstantsGlobals({
+ "%uint = OpTypeInt 32 0", "%_pf_uint = OpTypePointer Function %uint",
+ "%unsigned_1 = OpConstant %uint 1",
+ "%unsigned_1_duplicate = OpConstant %uint 1", // duplicated constant
+ })
+ .AppendInMain({
+ "%uint_var = OpVariable %_pf_uint Function",
+ "OpStore %uint_var %unsigned_1_duplicate",
+ });
+
+ expected_builder
+ .AppendTypesConstantsGlobals({
+ "%uint = OpTypeInt 32 0",
+ "%_pf_uint = OpTypePointer Function %uint",
+ "%unsigned_1 = OpConstant %uint 1",
+ })
+ .AppendInMain({
+ "%uint_var = OpVariable %_pf_uint Function",
+ "OpStore %uint_var %unsigned_1",
+ })
+ .AppendNames({
+ "OpName %unsigned_1 \"unsigned_1_duplicate\"", // the OpName
+ // instruction of the
+ // removed duplicated
+ // constant won't be
+ // erased.
+ });
+ Check(expected_builder, test_builder);
+}
+
+TEST_F(UnifyFrontEndConstantSingleTest, SkipWhenResultIdHasDecorations) {
+ AssemblyBuilder test_builder;
+ AssemblyBuilder expected_builder;
+
+ test_builder
+ .AppendAnnotations({
+ // So far we don't have valid decorations for constants. This is
+ // preparing for the future updates of SPIR-V.
+ // TODO(qining): change to a valid decoration once they are available.
+ "OpDecorate %f_1 RelaxedPrecision",
+ "OpDecorate %f_2_dup RelaxedPrecision",
+ })
+ .AppendTypesConstantsGlobals({
+ // clang-format off
+ "%float = OpTypeFloat 32",
+ "%_pf_float = OpTypePointer Function %float",
+ "%f_1 = OpConstant %float 1",
+ // %f_1 has decoration, so %f_1 will not be used to replace %f_1_dup.
+ "%f_1_dup = OpConstant %float 1",
+ "%f_2 = OpConstant %float 2",
+ // %_2_dup has decoration, so %f_2 will not replace %f_2_dup.
+ "%f_2_dup = OpConstant %float 2",
+ // no decoration for %f_3 or %f_3_dup, %f_3_dup should be replaced.
+ "%f_3 = OpConstant %float 3",
+ "%f_3_dup = OpConstant %float 3",
+ // clang-format on
+ })
+ .AppendInMain({
+ // clang-format off
+ "%f_var = OpVariable %_pf_float Function",
+ "OpStore %f_var %f_1_dup",
+ "OpStore %f_var %f_2_dup",
+ "OpStore %f_var %f_3_dup",
+ // clang-format on
+ });
+
+ expected_builder
+ .AppendAnnotations({
+ "OpDecorate %f_1 RelaxedPrecision",
+ "OpDecorate %f_2_dup RelaxedPrecision",
+ })
+ .AppendTypesConstantsGlobals({
+ // clang-format off
+ "%float = OpTypeFloat 32",
+ "%_pf_float = OpTypePointer Function %float",
+ "%f_1 = OpConstant %float 1",
+ "%f_1_dup = OpConstant %float 1",
+ "%f_2 = OpConstant %float 2",
+ "%f_2_dup = OpConstant %float 2",
+ "%f_3 = OpConstant %float 3",
+ // clang-format on
+ })
+ .AppendInMain({
+ // clang-format off
+ "%f_var = OpVariable %_pf_float Function",
+ "OpStore %f_var %f_1_dup",
+ "OpStore %f_var %f_2_dup",
+ "OpStore %f_var %f_3",
+ // clang-format on
+ })
+ .AppendNames({
+ "OpName %f_3 \"f_3_dup\"",
+ });
+
+ Check(expected_builder, test_builder);
+}
+
+TEST_F(UnifyFrontEndConstantSingleTest, UnifyWithDecorationOnTypes) {
+ AssemblyBuilder test_builder;
+ AssemblyBuilder expected_builder;
+
+ test_builder
+ .AppendAnnotations({
+ "OpMemberDecorate %flat_d 1 RelaxedPrecision",
+ })
+ .AppendTypesConstantsGlobals({
+ // clang-format off
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%flat = OpTypeStruct %int %float",
+ "%_pf_flat = OpTypePointer Function %flat",
+ // decorated flat struct
+ "%flat_d = OpTypeStruct %int %float",
+ "%_pf_flat_d = OpTypePointer Function %flat_d",
+ // perserved contants. %flat_1 and %flat_d has same members, but
+ // their type are different in decorations, so they should not be
+ // used to replace each other.
+ "%int_1 = OpConstant %int 1",
+ "%float_1 = OpConstant %float 1",
+ "%flat_1 = OpConstantComposite %flat %int_1 %float_1",
+ "%flat_d_1 = OpConstantComposite %flat_d %int_1 %float_1",
+ // duplicated constants.
+ "%flat_1_dup = OpConstantComposite %flat %int_1 %float_1",
+ "%flat_d_1_dup = OpConstantComposite %flat_d %int_1 %float_1",
+ // clang-format on
+ })
+ .AppendInMain({
+ "%flat_var = OpVariable %_pf_flat Function",
+ "OpStore %flat_var %flat_1_dup",
+ "%flat_d_var = OpVariable %_pf_flat_d Function",
+ "OpStore %flat_d_var %flat_d_1_dup",
+ });
+
+ expected_builder
+ .AppendAnnotations({
+ "OpMemberDecorate %flat_d 1 RelaxedPrecision",
+ })
+ .AppendTypesConstantsGlobals({
+ // clang-format off
+ "%int = OpTypeInt 32 1",
+ "%float = OpTypeFloat 32",
+ "%flat = OpTypeStruct %int %float",
+ "%_pf_flat = OpTypePointer Function %flat",
+ // decorated flat struct
+ "%flat_d = OpTypeStruct %int %float",
+ "%_pf_flat_d = OpTypePointer Function %flat_d",
+ "%int_1 = OpConstant %int 1",
+ "%float_1 = OpConstant %float 1",
+ "%flat_1 = OpConstantComposite %flat %int_1 %float_1",
+ "%flat_d_1 = OpConstantComposite %flat_d %int_1 %float_1",
+ // clang-format on
+ })
+ .AppendInMain({
+ "%flat_var = OpVariable %_pf_flat Function",
+ "OpStore %flat_var %flat_1",
+ "%flat_d_var = OpVariable %_pf_flat_d Function",
+ "OpStore %flat_d_var %flat_d_1",
+ })
+ .AppendNames({
+ "OpName %flat_1 \"flat_1_dup\"",
+ "OpName %flat_d_1 \"flat_d_1_dup\"",
+ });
+
+ Check(expected_builder, test_builder);
+}
+
+struct UnifyConstantTestCase {
+ // preserved constants.
+ std::vector<std::string> preserved_consts;
+ // expected uses of the preserved constants.
+ std::vector<std::string> use_preserved_consts;
+ // duplicated constants of the preserved constants.
+ std::vector<std::string> duplicate_consts;
+ // uses of the duplicated constants, expected to be updated to use the
+ // preserved constants.
+ std::vector<std::string> use_duplicate_consts;
+ // The updated OpName instructions that originally refer to duplicated
+ // constants.
+ std::vector<std::string> remapped_names;
+};
+
+using UnifyFrontEndConstantParamTest = UnifyConstantTest<
+ PassTest<::testing::TestWithParam<UnifyConstantTestCase>>>;
+
+TEST_P(UnifyFrontEndConstantParamTest, TestCase) {
+ auto& tc = GetParam();
+ AssemblyBuilder test_builder;
+ AssemblyBuilder expected_builder;
+ test_builder.AppendTypesConstantsGlobals(CommonTypes());
+ expected_builder.AppendTypesConstantsGlobals(CommonTypes());
+
+ test_builder.AppendTypesConstantsGlobals(tc.preserved_consts)
+ .AppendTypesConstantsGlobals(tc.duplicate_consts)
+ .AppendInMain(tc.use_duplicate_consts);
+
+ // Duplicated constants are killed in the expected output, and the debug
+ // instructions attached to those duplicated instructions will be migrated to
+ // the corresponding preserved constants.
+ expected_builder.AppendTypesConstantsGlobals(tc.preserved_consts)
+ .AppendInMain(tc.use_preserved_consts)
+ .AppendNames(tc.remapped_names);
+
+ Check(expected_builder, test_builder);
+}
+
+INSTANTIATE_TEST_CASE_P(Case, UnifyFrontEndConstantParamTest,
+ ::testing::ValuesIn(std::vector<UnifyConstantTestCase>({
+ // clang-format off
+ // basic tests for scalar constants
+ {
+ // preserved constants
+ {
+ "%bool_true = OpConstantTrue %bool",
+ "%signed_1 = OpConstant %int 1",
+ "%signed_minus_1 = OpConstant %int64 -1",
+ "%unsigned_max = OpConstant %uint64 18446744073709551615",
+ "%float_1 = OpConstant %float 1",
+ "%double_1 = OpConstant %double 1",
+ },
+ // use preserved constants in main
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "OpStore %bool_var %bool_true",
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %signed_1",
+ "%int64_var = OpVariable %_pf_int64 Function",
+ "OpStore %int64_var %signed_minus_1",
+ "%uint64_var = OpVariable %_pf_uint64 Function",
+ "OpStore %uint64_var %unsigned_max",
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %float_1",
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %double_var %double_1",
+ },
+ // duplicated constants
+ {
+ "%bool_true_duplicate = OpConstantTrue %bool",
+ "%signed_1_duplicate = OpConstant %int 1",
+ "%signed_minus_1_duplicate = OpConstant %int64 -1",
+ "%unsigned_max_duplicate = OpConstant %uint64 18446744073709551615",
+ "%float_1_duplicate = OpConstant %float 1",
+ "%double_1_duplicate = OpConstant %double 1",
+ },
+ // use duplicated constants in main
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "OpStore %bool_var %bool_true_duplicate",
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %signed_1_duplicate",
+ "%int64_var = OpVariable %_pf_int64 Function",
+ "OpStore %int64_var %signed_minus_1_duplicate",
+ "%uint64_var = OpVariable %_pf_uint64 Function",
+ "OpStore %uint64_var %unsigned_max_duplicate",
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %float_1_duplicate",
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %double_var %double_1_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %bool_true \"bool_true_duplicate\"",
+ "OpName %signed_1 \"signed_1_duplicate\"",
+ "OpName %signed_minus_1 \"signed_minus_1_duplicate\"",
+ "OpName %unsigned_max \"unsigned_max_duplicate\"",
+ "OpName %float_1 \"float_1_duplicate\"",
+ "OpName %double_1 \"double_1_duplicate\"",
+ },
+ },
+ // NaN in different bit patterns should not be unified, but the ones
+ // using same bit pattern should be unified.
+ {
+ // preserved constants
+ {
+ "%float_nan_1 = OpConstant %float 0x1.8p+128", // !2143289344, 7FC00000
+ "%float_nan_2 = OpConstant %float 0x1.800002p+128",// !2143289345 7FC00001
+ },
+ // use preserved constants in main
+ {
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %float_nan_1",
+ "OpStore %float_var %float_nan_2",
+ },
+ // duplicated constants
+ {
+ "%float_nan_1_duplicate = OpConstant %float 0x1.8p+128", // !2143289344, 7FC00000
+ "%float_nan_2_duplicate = OpConstant %float 0x1.800002p+128",// !2143289345, 7FC00001
+ },
+ // use duplicated constants in main
+ {
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %float_nan_1_duplicate",
+ "OpStore %float_var %float_nan_2_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %float_nan_1 \"float_nan_1_duplicate\"",
+ "OpName %float_nan_2 \"float_nan_2_duplicate\"",
+ },
+ },
+ // null values
+ {
+ // preserved constants
+ {
+ "%bool_null = OpConstantNull %bool",
+ "%signed_null = OpConstantNull %int",
+ "%signed_64_null = OpConstantNull %int64",
+ "%float_null = OpConstantNull %float",
+ "%double_null = OpConstantNull %double",
+ // zero-valued constants will not be unified with the equivalent
+ // null constants.
+ "%signed_zero = OpConstant %int 0",
+ },
+ // use preserved constants in main
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "OpStore %bool_var %bool_null",
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %signed_null",
+ "%int64_var = OpVariable %_pf_int64 Function",
+ "OpStore %int64_var %signed_64_null",
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %float_null",
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %double_var %double_null",
+ },
+ // duplicated constants
+ {
+ "%bool_null_duplicate = OpConstantNull %bool",
+ "%signed_null_duplicate = OpConstantNull %int",
+ "%signed_64_null_duplicate = OpConstantNull %int64",
+ "%float_null_duplicate = OpConstantNull %float",
+ "%double_null_duplicate = OpConstantNull %double",
+ },
+ // use duplicated constants in main
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "OpStore %bool_var %bool_null_duplicate",
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %signed_null_duplicate",
+ "%int64_var = OpVariable %_pf_int64 Function",
+ "OpStore %int64_var %signed_64_null_duplicate",
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %float_null_duplicate",
+ "%double_var = OpVariable %_pf_double Function",
+ "OpStore %double_var %double_null_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %bool_null \"bool_null_duplicate\"",
+ "OpName %signed_null \"signed_null_duplicate\"",
+ "OpName %signed_64_null \"signed_64_null_duplicate\"",
+ "OpName %float_null \"float_null_duplicate\"",
+ "OpName %double_null \"double_null_duplicate\"",
+ },
+ },
+ // constant sampler
+ {
+ // preserved constants
+ {
+ "%sampler = OpTypeSampler",
+ "%_pf_sampler = OpTypePointer Function %sampler",
+ "%sampler_1 = OpConstantSampler %sampler Repeat 0 Linear",
+ },
+ // use preserved constants in main
+ {
+ "%sampler_var = OpVariable %_pf_sampler Function",
+ "OpStore %sampler_var %sampler_1",
+ },
+ // duplicated constants
+ {
+ "%sampler_1_duplicate = OpConstantSampler %sampler Repeat 0 Linear",
+ },
+ // use duplicated constants in main
+ {
+ "%sampler_var = OpVariable %_pf_sampler Function",
+ "OpStore %sampler_var %sampler_1_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %sampler_1 \"sampler_1_duplicate\"",
+ },
+ },
+ // duplicate vector built from same ids.
+ {
+ // preserved constants
+ {
+ "%signed_1 = OpConstant %int 1",
+ "%signed_2 = OpConstant %int 2",
+ "%signed_3 = OpConstant %int 3",
+ "%signed_4 = OpConstant %int 4",
+ "%vec = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3 %signed_4",
+ },
+ // use preserved constants in main
+ {
+ "%vec_var = OpVariable %_pf_v4int Function",
+ "OpStore %vec_var %vec",
+ },
+ // duplicated constants
+ {
+ "%vec_duplicate = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3 %signed_4",
+ },
+ // use duplicated constants in main
+ {
+ "%vec_var = OpVariable %_pf_v4int Function",
+ "OpStore %vec_var %vec_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %vec \"vec_duplicate\"",
+ }
+ },
+ // duplicate vector built from duplicated ids.
+ {
+ // preserved constants
+ {
+ "%signed_1 = OpConstant %int 1",
+ "%signed_2 = OpConstant %int 2",
+ "%signed_3 = OpConstant %int 3",
+ "%signed_4 = OpConstant %int 4",
+ "%vec = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3 %signed_4",
+ },
+ // use preserved constants in main
+ {
+ "%vec_var = OpVariable %_pf_v4int Function",
+ "OpStore %vec_var %vec",
+ },
+ // duplicated constants
+ {
+ "%signed_3_duplicate = OpConstant %int 3",
+ "%signed_4_duplicate = OpConstant %int 4",
+ "%vec_duplicate = OpConstantComposite %v4int %signed_1 %signed_2 %signed_3_duplicate %signed_4_duplicate",
+ },
+ // use duplicated constants in main
+ {
+ "%vec_var = OpVariable %_pf_v4int Function",
+ "OpStore %vec_var %vec_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %signed_3 \"signed_3_duplicate\"",
+ "OpName %signed_4 \"signed_4_duplicate\"",
+ "OpName %vec \"vec_duplicate\"",
+ },
+ },
+ // flat struct
+ {
+ // preserved constants
+ {
+ "%bool_true = OpConstantTrue %bool",
+ "%signed_1 = OpConstant %int 1",
+ "%float_1 = OpConstant %float 1",
+ "%double_1 = OpConstant %double 1",
+ "%s = OpConstantComposite %flat_struct %bool_true %signed_1 %float_1 %double_1",
+ },
+ // use preserved constants in main
+ {
+ "%s_var = OpVariable %_pf_flat_struct Function",
+ "OpStore %s_var %s",
+ },
+ // duplicated constants
+ {
+ "%float_1_duplicate = OpConstant %float 1",
+ "%double_1_duplicate = OpConstant %double 1",
+ "%s_duplicate = OpConstantComposite %flat_struct %bool_true %signed_1 %float_1_duplicate %double_1_duplicate",
+ },
+ // use duplicated constants in main
+ {
+ "%s_var = OpVariable %_pf_flat_struct Function",
+ "OpStore %s_var %s_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %float_1 \"float_1_duplicate\"",
+ "OpName %double_1 \"double_1_duplicate\"",
+ "OpName %s \"s_duplicate\"",
+ },
+ },
+ // nested struct
+ {
+ // preserved constants
+ {
+ "%bool_true = OpConstantTrue %bool",
+ "%signed_1 = OpConstant %int 1",
+ "%float_1 = OpConstant %float 1",
+ "%double_1 = OpConstant %double 1",
+ "%inner = OpConstantComposite %inner_struct %bool_true %float_1",
+ "%outer = OpConstantComposite %outer_struct %inner %signed_1 %double_1",
+ },
+ // use preserved constants in main
+ {
+ "%outer_var = OpVariable %_pf_outer_struct Function",
+ "OpStore %outer_var %outer",
+ },
+ // duplicated constants
+ {
+ "%float_1_duplicate = OpConstant %float 1",
+ "%double_1_duplicate = OpConstant %double 1",
+ "%inner_duplicate = OpConstantComposite %inner_struct %bool_true %float_1_duplicate",
+ "%outer_duplicate = OpConstantComposite %outer_struct %inner_duplicate %signed_1 %double_1_duplicate",
+ },
+ // use duplicated constants in main
+ {
+ "%outer_var = OpVariable %_pf_outer_struct Function",
+ "OpStore %outer_var %outer_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %float_1 \"float_1_duplicate\"",
+ "OpName %double_1 \"double_1_duplicate\"",
+ "OpName %inner \"inner_duplicate\"",
+ "OpName %outer \"outer_duplicate\"",
+ },
+ },
+ // composite type null constants. Null constants and zero-valued
+ // constants should not be used to replace each other.
+ {
+ // preserved constants
+ {
+ "%bool_zero = OpConstantFalse %bool",
+ "%float_zero = OpConstant %float 0",
+ "%int_null = OpConstantNull %int",
+ "%double_null = OpConstantNull %double",
+ // inner_struct type null constant.
+ "%null_inner = OpConstantNull %inner_struct",
+ // zero-valued composite constant built from zero-valued constant
+ // component. inner_zero should not be replace by null_inner.
+ "%inner_zero = OpConstantComposite %inner_struct %bool_zero %float_zero",
+ // zero-valued composite contant built from zero-valued constants
+ // and null constants.
+ "%outer_zero = OpConstantComposite %outer_struct %inner_zero %int_null %double_null",
+ // outer_struct type null constant, it should not be replaced by
+ // outer_zero.
+ "%null_outer = OpConstantNull %outer_struct",
+ },
+ // use preserved constants in main
+ {
+ "%inner_var = OpVariable %_pf_inner_struct Function",
+ "OpStore %inner_var %inner_zero",
+ "OpStore %inner_var %null_inner",
+ "%outer_var = OpVariable %_pf_outer_struct Function",
+ "OpStore %outer_var %outer_zero",
+ "OpStore %outer_var %null_outer",
+ },
+ // duplicated constants
+ {
+ "%null_inner_dup = OpConstantNull %inner_struct",
+ "%null_outer_dup = OpConstantNull %outer_struct",
+ "%inner_zero_dup = OpConstantComposite %inner_struct %bool_zero %float_zero",
+ "%outer_zero_dup = OpConstantComposite %outer_struct %inner_zero_dup %int_null %double_null",
+ },
+ // use duplicated constants in main
+ {
+ "%inner_var = OpVariable %_pf_inner_struct Function",
+ "OpStore %inner_var %inner_zero_dup",
+ "OpStore %inner_var %null_inner_dup",
+ "%outer_var = OpVariable %_pf_outer_struct Function",
+ "OpStore %outer_var %outer_zero_dup",
+ "OpStore %outer_var %null_outer_dup",
+ },
+ // remapped names
+ {
+ "OpName %null_inner \"null_inner_dup\"",
+ "OpName %null_outer \"null_outer_dup\"",
+ "OpName %inner_zero \"inner_zero_dup\"",
+ "OpName %outer_zero \"outer_zero_dup\"",
+ },
+ },
+ // Spec Constants with SpecId decoration should be skipped.
+ {
+ // preserved constants
+ {
+ // Assembly builder will add OpDecorate SpecId instruction for the
+ // following spec constant instructions automatically.
+ "%spec_bool_1 = OpSpecConstantTrue %bool",
+ "%spec_bool_2 = OpSpecConstantTrue %bool",
+ "%spec_int_1 = OpSpecConstant %int 1",
+ "%spec_int_2 = OpSpecConstant %int 1",
+ },
+ // use preserved constants in main
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "OpStore %bool_var %spec_bool_1",
+ "OpStore %bool_var %spec_bool_2",
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %spec_int_1",
+ "OpStore %int_var %spec_int_2",
+ },
+ // duplicated constants. No duplicated instruction to remove in this
+ // case.
+ {},
+ // use duplicated constants in main. Same as the above 'use preserved
+ // constants in main' defined above, as no instruction should be
+ // removed in this case.
+ {
+ "%bool_var = OpVariable %_pf_bool Function",
+ "OpStore %bool_var %spec_bool_1",
+ "OpStore %bool_var %spec_bool_2",
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %spec_int_1",
+ "OpStore %int_var %spec_int_2",
+ },
+ // remapped names. No duplicated instruction removed, so this is
+ // empty.
+ {}
+ },
+ // spec constant composite
+ {
+ // preserved constants
+ {
+ "%spec_bool_true = OpSpecConstantTrue %bool",
+ "%spec_signed_1 = OpSpecConstant %int 1",
+ "%float_1 = OpConstant %float 1",
+ "%double_1 = OpConstant %double 1",
+ "%spec_inner = OpSpecConstantComposite %inner_struct %spec_bool_true %float_1",
+ "%spec_outer = OpSpecConstantComposite %outer_struct %spec_inner %spec_signed_1 %double_1",
+ "%spec_vec2 = OpSpecConstantComposite %v2float %float_1 %float_1",
+ },
+ // use preserved constants in main
+ {
+ "%outer_var = OpVariable %_pf_outer_struct Function",
+ "OpStore %outer_var %spec_outer",
+ "%v2float_var = OpVariable %_pf_v2float Function",
+ "OpStore %v2float_var %spec_vec2",
+ },
+ // duplicated constants
+ {
+ "%float_1_duplicate = OpConstant %float 1",
+ "%double_1_duplicate = OpConstant %double 1",
+ "%spec_inner_duplicate = OpSpecConstantComposite %inner_struct %spec_bool_true %float_1_duplicate",
+ "%spec_outer_duplicate = OpSpecConstantComposite %outer_struct %spec_inner_duplicate %spec_signed_1 %double_1_duplicate",
+ "%spec_vec2_duplicate = OpSpecConstantComposite %v2float %float_1 %float_1_duplicate",
+ },
+ // use duplicated constants in main
+ {
+ "%outer_var = OpVariable %_pf_outer_struct Function",
+ "OpStore %outer_var %spec_outer_duplicate",
+ "%v2float_var = OpVariable %_pf_v2float Function",
+ "OpStore %v2float_var %spec_vec2_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %float_1 \"float_1_duplicate\"",
+ "OpName %double_1 \"double_1_duplicate\"",
+ "OpName %spec_inner \"spec_inner_duplicate\"",
+ "OpName %spec_outer \"spec_outer_duplicate\"",
+ "OpName %spec_vec2 \"spec_vec2_duplicate\"",
+ },
+ },
+ // spec constant op with int scalar
+ {
+ // preserved constants
+ {
+ "%spec_signed_1 = OpSpecConstant %int 1",
+ "%spec_signed_2 = OpSpecConstant %int 2",
+ "%spec_signed_add = OpSpecConstantOp %int IAdd %spec_signed_1 %spec_signed_2",
+ },
+ // use preserved constants in main
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %spec_signed_add",
+ },
+ // duplicated constants
+ {
+ "%spec_signed_add_duplicate = OpSpecConstantOp %int IAdd %spec_signed_1 %spec_signed_2",
+ },
+ // use duplicated contants in main
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %spec_signed_add_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %spec_signed_add \"spec_signed_add_duplicate\"",
+ },
+ },
+ // spec constant op composite extract
+ {
+ // preserved constants
+ {
+ "%float_1 = OpConstant %float 1",
+ "%spec_vec2 = OpSpecConstantComposite %v2float %float_1 %float_1",
+ "%spec_extract = OpSpecConstantOp %float CompositeExtract %spec_vec2 1",
+ },
+ // use preserved constants in main
+ {
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %spec_extract",
+ },
+ // duplicated constants
+ {
+ "%spec_extract_duplicate = OpSpecConstantOp %float CompositeExtract %spec_vec2 1",
+ },
+ // use duplicated constants in main
+ {
+ "%float_var = OpVariable %_pf_float Function",
+ "OpStore %float_var %spec_extract_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %spec_extract \"spec_extract_duplicate\"",
+ },
+ },
+ // spec constant op vector shuffle
+ {
+ // preserved constants
+ {
+ "%float_1 = OpConstant %float 1",
+ "%float_2 = OpConstant %float 2",
+ "%spec_vec2_1 = OpSpecConstantComposite %v2float %float_1 %float_1",
+ "%spec_vec2_2 = OpSpecConstantComposite %v2float %float_2 %float_2",
+ "%spec_vector_shuffle = OpSpecConstantOp %v2float VectorShuffle %spec_vec2_1 %spec_vec2_2 1 2",
+ },
+ // use preserved constants in main
+ {
+ "%v2float_var = OpVariable %_pf_v2float Function",
+ "OpStore %v2float_var %spec_vector_shuffle",
+ },
+ // duplicated constants
+ {
+ "%spec_vector_shuffle_duplicate = OpSpecConstantOp %v2float VectorShuffle %spec_vec2_1 %spec_vec2_2 1 2",
+ },
+ // use duplicated constants in main
+ {
+ "%v2float_var = OpVariable %_pf_v2float Function",
+ "OpStore %v2float_var %spec_vector_shuffle_duplicate",
+ },
+ // remapped names
+ {
+ "OpName %spec_vector_shuffle \"spec_vector_shuffle_duplicate\"",
+ },
+ },
+ // long dependency chain
+ {
+ // preserved constants
+ {
+ "%array_size = OpConstant %int 4",
+ "%type_arr_int_4 = OpTypeArray %int %array_size",
+ "%signed_0 = OpConstant %int 100",
+ "%signed_1 = OpConstant %int 1",
+ "%signed_2 = OpSpecConstantOp %int IAdd %signed_0 %signed_1",
+ "%signed_3 = OpSpecConstantOp %int ISub %signed_0 %signed_2",
+ "%signed_4 = OpSpecConstantOp %int IAdd %signed_0 %signed_3",
+ "%signed_5 = OpSpecConstantOp %int ISub %signed_0 %signed_4",
+ "%signed_6 = OpSpecConstantOp %int IAdd %signed_0 %signed_5",
+ "%signed_7 = OpSpecConstantOp %int ISub %signed_0 %signed_6",
+ "%signed_8 = OpSpecConstantOp %int IAdd %signed_0 %signed_7",
+ "%signed_9 = OpSpecConstantOp %int ISub %signed_0 %signed_8",
+ "%signed_10 = OpSpecConstantOp %int IAdd %signed_0 %signed_9",
+ "%signed_11 = OpSpecConstantOp %int ISub %signed_0 %signed_10",
+ "%signed_12 = OpSpecConstantOp %int IAdd %signed_0 %signed_11",
+ "%signed_13 = OpSpecConstantOp %int ISub %signed_0 %signed_12",
+ "%signed_14 = OpSpecConstantOp %int IAdd %signed_0 %signed_13",
+ "%signed_15 = OpSpecConstantOp %int ISub %signed_0 %signed_14",
+ "%signed_16 = OpSpecConstantOp %int ISub %signed_0 %signed_15",
+ "%signed_17 = OpSpecConstantOp %int IAdd %signed_0 %signed_16",
+ "%signed_18 = OpSpecConstantOp %int ISub %signed_0 %signed_17",
+ "%signed_19 = OpSpecConstantOp %int IAdd %signed_0 %signed_18",
+ "%signed_20 = OpSpecConstantOp %int ISub %signed_0 %signed_19",
+ "%signed_vec_a = OpSpecConstantComposite %v2int %signed_18 %signed_19",
+ "%signed_vec_b = OpSpecConstantOp %v2int IMul %signed_vec_a %signed_vec_a",
+ "%signed_21 = OpSpecConstantOp %int CompositeExtract %signed_vec_b 0",
+ "%signed_array = OpConstantComposite %type_arr_int_4 %signed_20 %signed_20 %signed_21 %signed_21",
+ "%signed_22 = OpSpecConstantOp %int CompositeExtract %signed_array 0",
+ },
+ // use preserved constants in main
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %signed_22",
+ },
+ // duplicated constants
+ {
+ "%signed_0_dup = OpConstant %int 100",
+ "%signed_1_dup = OpConstant %int 1",
+ "%signed_2_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_1_dup",
+ "%signed_3_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_2_dup",
+ "%signed_4_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_3_dup",
+ "%signed_5_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_4_dup",
+ "%signed_6_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_5_dup",
+ "%signed_7_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_6_dup",
+ "%signed_8_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_7_dup",
+ "%signed_9_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_8_dup",
+ "%signed_10_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_9_dup",
+ "%signed_11_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_10_dup",
+ "%signed_12_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_11_dup",
+ "%signed_13_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_12_dup",
+ "%signed_14_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_13_dup",
+ "%signed_15_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_14_dup",
+ "%signed_16_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_15_dup",
+ "%signed_17_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_16_dup",
+ "%signed_18_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_17_dup",
+ "%signed_19_dup = OpSpecConstantOp %int IAdd %signed_0_dup %signed_18_dup",
+ "%signed_20_dup = OpSpecConstantOp %int ISub %signed_0_dup %signed_19_dup",
+ "%signed_vec_a_dup = OpSpecConstantComposite %v2int %signed_18_dup %signed_19_dup",
+ "%signed_vec_b_dup = OpSpecConstantOp %v2int IMul %signed_vec_a_dup %signed_vec_a_dup",
+ "%signed_21_dup = OpSpecConstantOp %int CompositeExtract %signed_vec_b_dup 0",
+ "%signed_array_dup = OpConstantComposite %type_arr_int_4 %signed_20_dup %signed_20_dup %signed_21_dup %signed_21_dup",
+ "%signed_22_dup = OpSpecConstantOp %int CompositeExtract %signed_array_dup 0",
+ },
+ // use duplicated constants in main
+ {
+ "%int_var = OpVariable %_pf_int Function",
+ "OpStore %int_var %signed_22_dup",
+ },
+ // remapped names
+ {
+ "OpName %signed_0 \"signed_0_dup\"",
+ "OpName %signed_1 \"signed_1_dup\"",
+ "OpName %signed_2 \"signed_2_dup\"",
+ "OpName %signed_3 \"signed_3_dup\"",
+ "OpName %signed_4 \"signed_4_dup\"",
+ "OpName %signed_5 \"signed_5_dup\"",
+ "OpName %signed_6 \"signed_6_dup\"",
+ "OpName %signed_7 \"signed_7_dup\"",
+ "OpName %signed_8 \"signed_8_dup\"",
+ "OpName %signed_9 \"signed_9_dup\"",
+ "OpName %signed_10 \"signed_10_dup\"",
+ "OpName %signed_11 \"signed_11_dup\"",
+ "OpName %signed_12 \"signed_12_dup\"",
+ "OpName %signed_13 \"signed_13_dup\"",
+ "OpName %signed_14 \"signed_14_dup\"",
+ "OpName %signed_15 \"signed_15_dup\"",
+ "OpName %signed_16 \"signed_16_dup\"",
+ "OpName %signed_17 \"signed_17_dup\"",
+ "OpName %signed_18 \"signed_18_dup\"",
+ "OpName %signed_19 \"signed_19_dup\"",
+ "OpName %signed_20 \"signed_20_dup\"",
+ "OpName %signed_vec_a \"signed_vec_a_dup\"",
+ "OpName %signed_vec_b \"signed_vec_b_dup\"",
+ "OpName %signed_21 \"signed_21_dup\"",
+ "OpName %signed_array \"signed_array_dup\"",
+ "OpName %signed_22 \"signed_22_dup\"",
+ },
+ },
+ // clang-format on
+ })));
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/upgrade_memory_model_test.cpp b/test/opt/upgrade_memory_model_test.cpp
new file mode 100644
index 0000000..9d2d762
--- /dev/null
+++ b/test/opt/upgrade_memory_model_test.cpp
@@ -0,0 +1,1716 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "assembly_builder.h"
+#include "gmock/gmock.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using UpgradeMemoryModelTest = opt::PassTest<::testing::Test>;
+
+TEST_F(UpgradeMemoryModelTest, InvalidMemoryModelOpenCL) {
+ const std::string text = R"(
+; CHECK: OpMemoryModel Logical OpenCL
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, InvalidMemoryModelVulkanKHR) {
+ const std::string text = R"(
+; CHECK: OpMemoryModel Logical VulkanKHR
+OpCapability Shader
+OpCapability Linkage
+OpCapability VulkanMemoryModelKHR
+OpExtension "SPV_KHR_vulkan_memory_model"
+OpMemoryModel Logical VulkanKHR
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, JustMemoryModel) {
+ const std::string text = R"(
+; CHECK: OpCapability VulkanMemoryModelKHR
+; CHECK: OpExtension "SPV_KHR_vulkan_memory_model"
+; CHECK: OpMemoryModel Logical VulkanKHR
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, RemoveDecorations) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var Volatile
+OpDecorate %var Coherent
+%int = OpTypeInt 32 0
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%var = OpVariable %ptr_int_Uniform Uniform
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, WorkgroupVariable) {
+ const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Workgroup = OpTypePointer Workgroup %int
+%var = OpVariable %ptr_int_Workgroup Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+%st = OpStore %var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, WorkgroupFunctionParameter) {
+ const std::string text = R"(
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Workgroup = OpTypePointer Workgroup %int
+%func_ty = OpTypeFunction %void %ptr_int_Workgroup
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_int_Workgroup
+%1 = OpLabel
+%ld = OpLoad %int %param
+%st = OpStore %param %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SimpleUniformVariable) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+OpDecorate %var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%var = OpVariable %ptr_int_Uniform Uniform
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+OpStore %var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SimpleUniformFunctionParameter) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %param Coherent
+OpDecorate %param Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%func_ty = OpTypeFunction %void %ptr_int_Uniform
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_int_Uniform
+%1 = OpLabel
+%ld = OpLoad %int %param
+OpStore %param %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SimpleUniformVariableOnlyVolatile) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK-NOT: OpConstant
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%var = OpVariable %ptr_int_Uniform Uniform
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+OpStore %var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SimpleUniformVariableCopied) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+OpDecorate %var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%var = OpVariable %ptr_int_Uniform Uniform
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%copy = OpCopyObject %ptr_int_Uniform %var
+%ld = OpLoad %int %copy
+OpStore %copy %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SimpleUniformFunctionParameterCopied) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %param Coherent
+OpDecorate %param Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%func_ty = OpTypeFunction %void %ptr_int_Uniform
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_int_Uniform
+%1 = OpLabel
+%copy = OpCopyObject %ptr_int_Uniform %param
+%ld = OpLoad %int %copy
+%copy2 = OpCopyObject %ptr_int_Uniform %param
+OpStore %copy2 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SimpleUniformVariableAccessChain) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+OpDecorate %var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int3 = OpConstant %int 3
+%int_array_3 = OpTypeArray %int %int3
+%ptr_intarray_Uniform = OpTypePointer Uniform %int_array_3
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%var = OpVariable %ptr_intarray_Uniform Uniform
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%gep = OpAccessChain %ptr_int_Uniform %var %int0
+%ld = OpLoad %int %gep
+OpStore %gep %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, SimpleUniformFunctionParameterAccessChain) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpMemoryModel Logical GLSL450
+OpDecorate %param Coherent
+OpDecorate %param Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int3 = OpConstant %int 3
+%int_array_3 = OpTypeArray %int %int3
+%ptr_intarray_Uniform = OpTypePointer Uniform %int_array_3
+%ptr_int_Uniform = OpTypePointer Uniform %int
+%func_ty = OpTypeFunction %void %ptr_intarray_Uniform
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_intarray_Uniform
+%1 = OpLabel
+%ld_gep = OpAccessChain %ptr_int_Uniform %param %int0
+%ld = OpLoad %int %ld_gep
+%st_gep = OpAccessChain %ptr_int_Uniform %param %int0
+OpStore %st_gep %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VariablePointerSelect) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+OpDecorate %var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%null = OpConstantNull %ptr_int_StorageBuffer
+%var = OpVariable %ptr_int_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%select = OpSelect %ptr_int_StorageBuffer %true %var %null
+%ld = OpLoad %int %select
+OpStore %var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VariablePointerSelectConservative) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpDecorate %var1 Coherent
+OpDecorate %var2 Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%var1 = OpVariable %ptr_int_StorageBuffer StorageBuffer
+%var2 = OpVariable %ptr_int_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%select = OpSelect %ptr_int_StorageBuffer %true %var1 %var2
+%ld = OpLoad %int %select
+OpStore %select %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VariablePointerIncrement) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate {{%\w+}} Coherent
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability VariablePointers
+OpExtension "SPV_KHR_variable_pointers"
+OpMemoryModel Logical GLSL450
+OpDecorate %param Coherent
+OpDecorate %param ArrayStride 4
+%void = OpTypeVoid
+%bool = OpTypeBool
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%int10 = OpConstant %int 10
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_int_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_int_StorageBuffer
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+%phi = OpPhi %ptr_int_StorageBuffer %param %1 %ptr_next %2
+%iv = OpPhi %int %int0 %1 %inc %2
+%inc = OpIAdd %int %iv %int1
+%ptr_next = OpPtrAccessChain %ptr_int_StorageBuffer %phi %int1
+%cmp = OpIEqual %bool %iv %int10
+OpLoopMerge %3 %2 None
+OpBranchConditional %cmp %3 %2
+%3 = OpLabel
+%ld = OpLoad %int %phi
+OpStore %phi %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentStructElement) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %struct 0 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%struct = OpTypeStruct %int
+%ptr_struct_StorageBuffer = OpTypePointer StorageBuffer %struct
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_struct_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_struct_StorageBuffer
+%1 = OpLabel
+%gep = OpAccessChain %ptr_int_StorageBuffer %param %int0
+%ld = OpLoad %int %gep
+OpStore %gep %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentElementFullStructAccess) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %struct 0 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%struct = OpTypeStruct %int
+%ptr_struct_StorageBuffer = OpTypePointer StorageBuffer %struct
+%func_ty = OpTypeFunction %void %ptr_struct_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_struct_StorageBuffer
+%1 = OpLabel
+%ld = OpLoad %struct %param
+OpStore %param %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentElementNotAccessed) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK-NOT: MakePointerAvailableKHR
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK-NOT: MakePointerVisibleKHR
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %struct 1 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%struct = OpTypeStruct %int %int
+%ptr_struct_StorageBuffer = OpTypePointer StorageBuffer %struct
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_struct_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_struct_StorageBuffer
+%1 = OpLabel
+%gep = OpAccessChain %ptr_int_StorageBuffer %param %int0
+%ld = OpLoad %int %gep
+OpStore %gep %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, MultiIndexAccessCoherent) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %inner 1 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%inner = OpTypeStruct %int %int
+%middle = OpTypeStruct %inner
+%outer = OpTypeStruct %middle %middle
+%ptr_outer_StorageBuffer = OpTypePointer StorageBuffer %outer
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_outer_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_outer_StorageBuffer
+%1 = OpLabel
+%ld_gep = OpInBoundsAccessChain %ptr_int_StorageBuffer %param %int0 %int0 %int1
+%ld = OpLoad %int %ld_gep
+%st_gep = OpInBoundsAccessChain %ptr_int_StorageBuffer %param %int1 %int0 %int1
+OpStore %st_gep %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, MultiIndexAccessNonCoherent) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK-NOT: MakePointerAvailableKHR
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK-NOT: MakePointerVisibleKHR
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %inner 1 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%inner = OpTypeStruct %int %int
+%middle = OpTypeStruct %inner
+%outer = OpTypeStruct %middle %middle
+%ptr_outer_StorageBuffer = OpTypePointer StorageBuffer %outer
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_outer_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_outer_StorageBuffer
+%1 = OpLabel
+%ld_gep = OpInBoundsAccessChain %ptr_int_StorageBuffer %param %int0 %int0 %int0
+%ld = OpLoad %int %ld_gep
+%st_gep = OpInBoundsAccessChain %ptr_int_StorageBuffer %param %int1 %int0 %int0
+OpStore %st_gep %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, ConsecutiveAccessChainCoherent) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %inner 1 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%inner = OpTypeStruct %int %int
+%middle = OpTypeStruct %inner
+%outer = OpTypeStruct %middle %middle
+%ptr_outer_StorageBuffer = OpTypePointer StorageBuffer %outer
+%ptr_middle_StorageBuffer = OpTypePointer StorageBuffer %middle
+%ptr_inner_StorageBuffer = OpTypePointer StorageBuffer %inner
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_outer_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_outer_StorageBuffer
+%1 = OpLabel
+%ld_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int0
+%ld_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %ld_gep1 %int0
+%ld_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %ld_gep2 %int1
+%ld = OpLoad %int %ld_gep3
+%st_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int1
+%st_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %st_gep1 %int0
+%st_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %st_gep2 %int1
+OpStore %st_gep3 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, ConsecutiveAccessChainNonCoherent) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK-NOT: MakePointerAvailableKHR
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK-NOT: MakePointerVisibleKHR
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %inner 1 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%inner = OpTypeStruct %int %int
+%middle = OpTypeStruct %inner
+%outer = OpTypeStruct %middle %middle
+%ptr_outer_StorageBuffer = OpTypePointer StorageBuffer %outer
+%ptr_middle_StorageBuffer = OpTypePointer StorageBuffer %middle
+%ptr_inner_StorageBuffer = OpTypePointer StorageBuffer %inner
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_outer_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_outer_StorageBuffer
+%1 = OpLabel
+%ld_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int0
+%ld_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %ld_gep1 %int0
+%ld_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %ld_gep2 %int0
+%ld = OpLoad %int %ld_gep3
+%st_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int1
+%st_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %st_gep1 %int0
+%st_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %st_gep2 %int0
+OpStore %st_gep3 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentStructElementAccess) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %middle 0 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%inner = OpTypeStruct %int %int
+%middle = OpTypeStruct %inner
+%outer = OpTypeStruct %middle %middle
+%ptr_outer_StorageBuffer = OpTypePointer StorageBuffer %outer
+%ptr_middle_StorageBuffer = OpTypePointer StorageBuffer %middle
+%ptr_inner_StorageBuffer = OpTypePointer StorageBuffer %inner
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_outer_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_outer_StorageBuffer
+%1 = OpLabel
+%ld_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int0
+%ld_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %ld_gep1 %int0
+%ld_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %ld_gep2 %int1
+%ld = OpLoad %int %ld_gep3
+%st_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int1
+%st_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %st_gep1 %int0
+%st_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %st_gep2 %int1
+OpStore %st_gep3 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, NonCoherentLoadCoherentStore) {
+ const std::string text = R"(
+; CHECK-NOT: OpMemberDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK-NOT: MakePointerVisibleKHR
+; CHECK: OpStore {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpMemberDecorate %outer 1 Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int0 = OpConstant %int 0
+%int1 = OpConstant %int 1
+%inner = OpTypeStruct %int %int
+%middle = OpTypeStruct %inner
+%outer = OpTypeStruct %middle %middle
+%ptr_outer_StorageBuffer = OpTypePointer StorageBuffer %outer
+%ptr_middle_StorageBuffer = OpTypePointer StorageBuffer %middle
+%ptr_inner_StorageBuffer = OpTypePointer StorageBuffer %inner
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_outer_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_outer_StorageBuffer
+%1 = OpLabel
+%ld_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int0
+%ld_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %ld_gep1 %int0
+%ld_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %ld_gep2 %int1
+%ld = OpLoad %int %ld_gep3
+%st_gep1 = OpInBoundsAccessChain %ptr_middle_StorageBuffer %param %int1
+%st_gep2 = OpInBoundsAccessChain %ptr_inner_StorageBuffer %st_gep1 %int0
+%st_gep3 = OpInBoundsAccessChain %ptr_int_StorageBuffer %st_gep2 %int1
+OpStore %st_gep3 %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CopyMemory) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[queuefamily:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Volatile|MakePointerVisibleKHR|NonPrivatePointerKHR [[queuefamily]]
+; CHECK-NOT: [[queuefamily]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %in_var Coherent
+OpDecorate %out_var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%in_var = OpVariable %ptr_int_StorageBuffer StorageBuffer
+%out_var = OpVariable %ptr_int_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+OpCopyMemory %out_var %in_var
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CopyMemorySized) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[queuefamily:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpCopyMemorySized {{%\w+}} {{%\w+}} {{%\w+}} Volatile|MakePointerAvailableKHR|NonPrivatePointerKHR [[queuefamily]]
+; CHECK-NOT: [[queuefamily]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability Addresses
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %out_param Coherent
+OpDecorate %in_param Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%int4 = OpConstant %int 4
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%func_ty = OpTypeFunction %void %ptr_int_StorageBuffer %ptr_int_StorageBuffer
+%func = OpFunction %void None %func_ty
+%in_param = OpFunctionParameter %ptr_int_StorageBuffer
+%out_param = OpFunctionParameter %ptr_int_StorageBuffer
+%1 = OpLabel
+OpCopyMemorySized %out_param %in_param %int4
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CopyMemoryTwoScopes) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK-DAG: [[queuefamily:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK-DAG: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|MakePointerVisibleKHR|NonPrivatePointerKHR [[workgroup]] [[queuefamily]]
+OpCapability Shader
+OpCapability Linkage
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %in_var Coherent
+OpDecorate %out_var Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%ptr_int_Workgroup = OpTypePointer Workgroup %int
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%in_var = OpVariable %ptr_int_StorageBuffer StorageBuffer
+%out_var = OpVariable %ptr_int_Workgroup Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+OpCopyMemory %out_var %in_var
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileImageRead) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile
+; CHECK: OpImageRead {{%\w+}} {{%\w+}} {{%\w+}} VolatileTexelKHR
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageReadWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%int0 = OpConstant %int 0
+%v2int_0 = OpConstantComposite %v2int %int0 %int0
+%image = OpTypeImage %float 2D 0 0 0 2 Unknown
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%var = OpVariable %ptr_image_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %image %var
+%rd = OpImageRead %float %ld %v2int_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentImageRead) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpImageRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageReadWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%int0 = OpConstant %int 0
+%v2int_0 = OpConstantComposite %v2int %int0 %int0
+%image = OpTypeImage %float 2D 0 0 0 2 Unknown
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%var = OpVariable %ptr_image_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %image %var
+%rd = OpImageRead %float %ld %v2int_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentImageReadExtractedFromSampledImage) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[image:%\w+]] = OpTypeImage
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad [[image]] {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK: OpImageRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageReadWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%int0 = OpConstant %int 0
+%v2int_0 = OpConstantComposite %v2int %int0 %int0
+%image = OpTypeImage %float 2D 0 0 0 0 Unknown
+%sampled_image = OpTypeSampledImage %image
+%sampler = OpTypeSampler
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%ptr_sampler_StorageBuffer = OpTypePointer StorageBuffer %sampler
+%var = OpVariable %ptr_image_StorageBuffer StorageBuffer
+%sampler_var = OpVariable %ptr_sampler_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %image %var
+%ld_sampler = OpLoad %sampler %sampler_var
+%sample = OpSampledImage %sampled_image %ld %ld_sampler
+%extract = OpImage %image %sample
+%rd = OpImageRead %float %extract %v2int_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileImageWrite) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile
+; CHECK: OpImageWrite {{%\w+}} {{%\w+}} {{%\w+}} VolatileTexelKHR
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageWriteWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %param Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%float0 = OpConstant %float 0
+%v2int_null = OpConstantNull %v2int
+%image = OpTypeImage %float 2D 0 0 0 0 Unknown
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%func_ty = OpTypeFunction %void %ptr_image_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_image_StorageBuffer
+%1 = OpLabel
+%ld = OpLoad %image %param
+OpImageWrite %ld %v2int_null %float0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentImageWrite) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR
+; CHECK: OpImageWrite {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageWriteWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %param Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%float0 = OpConstant %float 0
+%v2int_null = OpConstantNull %v2int
+%image = OpTypeImage %float 2D 0 0 0 0 Unknown
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%func_ty = OpTypeFunction %void %ptr_image_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_image_StorageBuffer
+%1 = OpLabel
+%ld = OpLoad %image %param
+OpImageWrite %ld %v2int_null %float0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentImageWriteExtractFromSampledImage) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK: OpImageWrite {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelAvailableKHR|NonPrivateTexelKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageWriteWithoutFormat
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %param Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%float0 = OpConstant %float 0
+%v2int_null = OpConstantNull %v2int
+%image = OpTypeImage %float 2D 0 0 0 0 Unknown
+%sampled_image = OpTypeSampledImage %image
+%sampler = OpTypeSampler
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%ptr_sampler_StorageBuffer = OpTypePointer StorageBuffer %sampler
+%func_ty = OpTypeFunction %void %ptr_image_StorageBuffer %ptr_sampler_StorageBuffer
+%func = OpFunction %void None %func_ty
+%param = OpFunctionParameter %ptr_image_StorageBuffer
+%sampler_param = OpFunctionParameter %ptr_sampler_StorageBuffer
+%1 = OpLabel
+%ld = OpLoad %image %param
+%ld_sampler = OpLoad %sampler %sampler_param
+%sample = OpSampledImage %sampled_image %ld %ld_sampler
+%extract = OpImage %image %sample
+OpImageWrite %extract %v2int_null %float0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, VolatileImageSparseRead) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: OpLoad {{%\w+}} {{%\w+}} Volatile
+; CHECK: OpImageSparseRead {{%\w+}} {{%\w+}} {{%\w+}} VolatileTexelKHR
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageReadWithoutFormat
+OpCapability SparseResidency
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %var Volatile
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%int0 = OpConstant %int 0
+%v2int_0 = OpConstantComposite %v2int %int0 %int0
+%image = OpTypeImage %float 2D 0 0 0 2 Unknown
+%struct = OpTypeStruct %int %float
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%var = OpVariable %ptr_image_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %image %var
+%rd = OpImageSparseRead %struct %ld %v2int_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, CoherentImageSparseRead) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad {{%\w+}} {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK: OpImageSparseRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageReadWithoutFormat
+OpCapability SparseResidency
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%int0 = OpConstant %int 0
+%v2int_0 = OpConstantComposite %v2int %int0 %int0
+%image = OpTypeImage %float 2D 0 0 0 2 Unknown
+%struct = OpTypeStruct %int %float
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%var = OpVariable %ptr_image_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %image %var
+%rd = OpImageSparseRead %struct %ld %v2int_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest,
+ CoherentImageSparseReadExtractedFromSampledImage) {
+ const std::string text = R"(
+; CHECK-NOT: OpDecorate
+; CHECK: [[image:%\w+]] = OpTypeImage
+; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpLoad [[image]] {{%\w+}} MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK: OpImageSparseRead {{%\w+}} {{%\w+}} {{%\w+}} MakeTexelVisibleKHR|NonPrivateTexelKHR [[scope]]
+OpCapability Shader
+OpCapability Linkage
+OpCapability StorageImageReadWithoutFormat
+OpCapability SparseResidency
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpDecorate %var Coherent
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%v2int = OpTypeVector %int 2
+%float = OpTypeFloat 32
+%int0 = OpConstant %int 0
+%v2int_0 = OpConstantComposite %v2int %int0 %int0
+%image = OpTypeImage %float 2D 0 0 0 0 Unknown
+%struct = OpTypeStruct %int %float
+%sampled_image = OpTypeSampledImage %image
+%sampler = OpTypeSampler
+%ptr_image_StorageBuffer = OpTypePointer StorageBuffer %image
+%ptr_sampler_StorageBuffer = OpTypePointer StorageBuffer %sampler
+%var = OpVariable %ptr_image_StorageBuffer StorageBuffer
+%sampler_var = OpVariable %ptr_sampler_StorageBuffer StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %image %var
+%ld_sampler = OpLoad %sampler %sampler_var
+%sample = OpSampledImage %sampled_image %ld %ld_sampler
+%extract = OpImage %image %sample
+%rd = OpImageSparseRead %struct %extract %v2int_0
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, TessellationControlBarrierNoChange) {
+ const std::string text = R"(
+; CHECK: [[none:%\w+]] = OpConstant {{%\w+}} 0
+; CHECK: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpControlBarrier [[workgroup]] [[workgroup]] [[none]]
+OpCapability Tessellation
+OpMemoryModel Logical GLSL450
+OpEntryPoint TessellationControl %func "func"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%workgroup = OpConstant %int 2
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+OpControlBarrier %workgroup %workgroup %none
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, TessellationControlBarrierAddOutput) {
+ const std::string text = R"(
+; CHECK: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[output:%\w+]] = OpConstant {{%\w+}} 4096
+; CHECK: OpControlBarrier [[workgroup]] [[workgroup]] [[output]]
+OpCapability Tessellation
+OpMemoryModel Logical GLSL450
+OpEntryPoint TessellationControl %func "func" %var
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%workgroup = OpConstant %int 2
+%ptr_int_Output = OpTypePointer Output %int
+%var = OpVariable %ptr_int_Output Output
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+OpControlBarrier %workgroup %workgroup %none
+OpStore %var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, TessellationMemoryBarrierNoChange) {
+ const std::string text = R"(
+; CHECK: [[none:%\w+]] = OpConstant {{%\w+}} 0
+; CHECK: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: OpMemoryBarrier [[workgroup]] [[none]]
+OpCapability Tessellation
+OpMemoryModel Logical GLSL450
+OpEntryPoint TessellationControl %func "func" %var
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%workgroup = OpConstant %int 2
+%ptr_int_Output = OpTypePointer Output %int
+%var = OpVariable %ptr_int_Output Output
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpLoad %int %var
+OpMemoryBarrier %workgroup %none
+OpStore %var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, TessellationControlBarrierAddOutputSubFunction) {
+ const std::string text = R"(
+; CHECK: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[output:%\w+]] = OpConstant {{%\w+}} 4096
+; CHECK: OpControlBarrier [[workgroup]] [[workgroup]] [[output]]
+OpCapability Tessellation
+OpMemoryModel Logical GLSL450
+OpEntryPoint TessellationControl %func "func" %var
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%workgroup = OpConstant %int 2
+%ptr_int_Output = OpTypePointer Output %int
+%var = OpVariable %ptr_int_Output Output
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%call = OpFunctionCall %void %sub_func
+OpReturn
+OpFunctionEnd
+%sub_func = OpFunction %void None %func_ty
+%2 = OpLabel
+%ld = OpLoad %int %var
+OpControlBarrier %workgroup %workgroup %none
+OpStore %var %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest,
+ TessellationControlBarrierAddOutputDifferentFunctions) {
+ const std::string text = R"(
+; CHECK: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[output:%\w+]] = OpConstant {{%\w+}} 4096
+; CHECK: OpControlBarrier [[workgroup]] [[workgroup]] [[output]]
+OpCapability Tessellation
+OpMemoryModel Logical GLSL450
+OpEntryPoint TessellationControl %func "func" %var
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%workgroup = OpConstant %int 2
+%ptr_int_Output = OpTypePointer Output %int
+%var = OpVariable %ptr_int_Output Output
+%func_ty = OpTypeFunction %void
+%ld_func_ty = OpTypeFunction %int
+%st_func_ty = OpTypeFunction %void %int
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%call_ld = OpFunctionCall %int %ld_func
+%call_barrier = OpFunctionCall %void %barrier_func
+%call_st = OpFunctionCall %void %st_func %call_ld
+OpReturn
+OpFunctionEnd
+%ld_func = OpFunction %int None %ld_func_ty
+%2 = OpLabel
+%ld = OpLoad %int %var
+OpReturnValue %ld
+OpFunctionEnd
+%barrier_func = OpFunction %void None %func_ty
+%3 = OpLabel
+OpControlBarrier %workgroup %workgroup %none
+OpReturn
+OpFunctionEnd
+%st_func = OpFunction %void None %st_func_ty
+%param = OpFunctionParameter %int
+%4 = OpLabel
+OpStore %var %param
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, ChangeControlBarrierMemoryScope) {
+ std::string text = R"(
+; CHECK: [[workgroup:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[queuefamily:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpControlBarrier [[workgroup]] [[queuefamily]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%device = OpConstant %int 1
+%workgroup = OpConstant %int 2
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+OpControlBarrier %workgroup %device %none
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, ChangeMemoryBarrierMemoryScope) {
+ std::string text = R"(
+; CHECK: [[queuefamily:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: OpMemoryBarrier [[queuefamily]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%device = OpConstant %int 1
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+OpMemoryBarrier %device %none
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, ChangeAtomicMemoryScope) {
+ std::string text = R"(
+; CHECK: [[int:%\w+]] = OpTypeInt
+; CHECK: [[var:%\w+]] = OpVariable
+; CHECK: [[qf:%\w+]] = OpConstant [[int]] 5
+; CHECK: OpAtomicLoad [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicStore [[var]] [[qf]]
+; CHECK: OpAtomicExchange [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicCompareExchange [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicIIncrement [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicIDecrement [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicIAdd [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicISub [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicSMin [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicSMax [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicUMin [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicUMax [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicAnd [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicOr [[int]] [[var]] [[qf]]
+; CHECK: OpAtomicXor [[int]] [[var]] [[qf]]
+OpCapability Shader
+OpExtension "SPV_KHR_storage_buffer_storage_class"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %func "func"
+%void = OpTypeVoid
+%int = OpTypeInt 32 0
+%none = OpConstant %int 0
+%device = OpConstant %int 1
+%func_ty = OpTypeFunction %void
+%ptr_int_StorageBuffer = OpTypePointer StorageBuffer %int
+%var = OpVariable %ptr_int_StorageBuffer StorageBuffer
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%ld = OpAtomicLoad %int %var %device %none
+OpAtomicStore %var %device %none %ld
+%ex = OpAtomicExchange %int %var %device %none %ld
+%cmp_ex = OpAtomicCompareExchange %int %var %device %none %none %ld %ld
+%inc = OpAtomicIIncrement %int %var %device %none
+%dec = OpAtomicIDecrement %int %var %device %none
+%add = OpAtomicIAdd %int %var %device %none %ld
+%sub = OpAtomicISub %int %var %device %none %ld
+%smin = OpAtomicSMin %int %var %device %none %ld
+%smax = OpAtomicSMax %int %var %device %none %ld
+%umin = OpAtomicUMin %int %var %device %none %ld
+%umax = OpAtomicUMax %int %var %device %none %ld
+%and = OpAtomicAnd %int %var %device %none %ld
+%or = OpAtomicOr %int %var %device %none %ld
+%xor = OpAtomicXor %int %var %device %none %ld
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeModfNoFlags) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]]
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%ssbo_var = OpVariable %ptr_ssbo_float StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeModfWorkgroupCoherent) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer Workgroup [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] Workgroup
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[wg_scope:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[wg_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_wg_float = OpTypePointer Workgroup %float
+%wg_var = OpVariable %ptr_wg_float Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeModfSSBOCoherent) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[qf_scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[qf_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %ssbo_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%ssbo_var = OpVariable %ptr_ssbo_float StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeModfSSBOVolatile) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[float]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[float]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} ModfStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] Volatile
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Volatile
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%ptr_ssbo_float = OpTypePointer StorageBuffer %float
+%wg_var = OpVariable %ptr_ssbo_float StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Modf %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpNoFlags) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]]
+; CHECK-NOT: NonPrivatePointerKHR
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpWorkgroupCoherent) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer Workgroup [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] Workgroup
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[wg_scope:%\w+]] = OpConstant {{%\w+}} 2
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[wg_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_wg_int = OpTypePointer Workgroup %int
+%wg_var = OpVariable %ptr_wg_int Workgroup
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpSSBOCoherent) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[qf_scope:%\w+]] = OpConstant {{%\w+}} 5
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] MakePointerAvailableKHR|NonPrivatePointerKHR [[qf_scope]]
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %ssbo_var Coherent
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%ssbo_var = OpVariable %ptr_ssbo_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %ssbo_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+TEST_F(UpgradeMemoryModelTest, UpgradeFrexpSSBOVolatile) {
+ const std::string text = R"(
+; CHECK: [[float:%\w+]] = OpTypeFloat 32
+; CHECK: [[float_0:%\w+]] = OpConstant [[float]] 0
+; CHECK: [[int:%\w+]] = OpTypeInt 32 0
+; CHECK: [[ptr:%\w+]] = OpTypePointer StorageBuffer [[int]]
+; CHECK: [[var:%\w+]] = OpVariable [[ptr]] StorageBuffer
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[float]] [[int]]
+; CHECK: [[modfstruct:%\w+]] = OpExtInst [[struct]] {{%\w+}} FrexpStruct [[float_0]]
+; CHECK: [[ex0:%\w+]] = OpCompositeExtract [[float]] [[modfstruct]] 0
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract [[int]] [[modfstruct]] 1
+; CHECK: OpStore [[var]] [[ex1]] Volatile
+; CHECK: OpFAdd [[float]] [[float_0]] [[ex0]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+%import = OpExtInstImport "GLSL.std.450"
+OpEntryPoint GLCompute %func "func"
+OpDecorate %wg_var Volatile
+%void = OpTypeVoid
+%float = OpTypeFloat 32
+%float_0 = OpConstant %float 0
+%int = OpTypeInt 32 0
+%ptr_ssbo_int = OpTypePointer StorageBuffer %int
+%wg_var = OpVariable %ptr_ssbo_int StorageBuffer
+%func_ty = OpTypeFunction %void
+%func = OpFunction %void None %func_ty
+%1 = OpLabel
+%2 = OpExtInst %float %import Frexp %float_0 %wg_var
+%3 = OpFAdd %float %float_0 %2
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
+}
+
+} // namespace
diff --git a/test/opt/utils_test.cpp b/test/opt/utils_test.cpp
new file mode 100644
index 0000000..9bb82a3
--- /dev/null
+++ b/test/opt/utils_test.cpp
@@ -0,0 +1,110 @@
+// Copyright (c) 2016 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 <vector>
+
+#include "gtest/gtest.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+TEST(JoinAllInsts, Cases) {
+ EXPECT_EQ("", JoinAllInsts({}));
+ EXPECT_EQ("a\n", JoinAllInsts({"a"}));
+ EXPECT_EQ("a\nb\n", JoinAllInsts({"a", "b"}));
+ EXPECT_EQ("a\nb\nc\n", JoinAllInsts({"a", "b", "c"}));
+ EXPECT_EQ("hello,\nworld!\n\n\n", JoinAllInsts({"hello,", "world!", "\n"}));
+}
+
+TEST(JoinNonDebugInsts, Cases) {
+ EXPECT_EQ("", JoinNonDebugInsts({}));
+ EXPECT_EQ("a\n", JoinNonDebugInsts({"a"}));
+ EXPECT_EQ("", JoinNonDebugInsts({"OpName"}));
+ EXPECT_EQ("a\nb\n", JoinNonDebugInsts({"a", "b"}));
+ EXPECT_EQ("", JoinNonDebugInsts({"OpName", "%1 = OpString \"42\""}));
+ EXPECT_EQ("Opstring\n", JoinNonDebugInsts({"OpName", "Opstring"}));
+ EXPECT_EQ("the only remaining string\n",
+ JoinNonDebugInsts(
+ {"OpSourceContinued", "OpSource", "OpSourceExtension",
+ "lgtm OpName", "hello OpMemberName", "this is a OpString",
+ "lonely OpLine", "happy OpNoLine", "OpModuleProcessed",
+ "the only remaining string"}));
+}
+
+struct SubstringReplacementTestCase {
+ const char* orig_str;
+ const char* find_substr;
+ const char* replace_substr;
+ const char* expected_str;
+ bool replace_should_succeed;
+};
+
+using FindAndReplaceTest =
+ ::testing::TestWithParam<SubstringReplacementTestCase>;
+
+TEST_P(FindAndReplaceTest, SubstringReplacement) {
+ auto process = std::string(GetParam().orig_str);
+ EXPECT_EQ(GetParam().replace_should_succeed,
+ FindAndReplace(&process, GetParam().find_substr,
+ GetParam().replace_substr))
+ << "Original string: " << GetParam().orig_str
+ << " replace: " << GetParam().find_substr
+ << " to: " << GetParam().replace_substr
+ << " should returns: " << GetParam().replace_should_succeed;
+ EXPECT_STREQ(GetParam().expected_str, process.c_str())
+ << "Original string: " << GetParam().orig_str
+ << " replace: " << GetParam().find_substr
+ << " to: " << GetParam().replace_substr
+ << " expected string: " << GetParam().expected_str;
+}
+
+INSTANTIATE_TEST_CASE_P(
+ SubstringReplacement, FindAndReplaceTest,
+ ::testing::ValuesIn(std::vector<SubstringReplacementTestCase>({
+ // orig string, find substring, replace substring, expected string,
+ // replacement happened
+ {"", "", "", "", false},
+ {"", "b", "", "", false},
+ {"", "", "c", "", false},
+ {"", "a", "b", "", false},
+
+ {"a", "", "c", "a", false},
+ {"a", "b", "c", "a", false},
+ {"a", "b", "", "a", false},
+ {"a", "a", "", "", true},
+ {"a", "a", "b", "b", true},
+
+ {"ab", "a", "b", "bb", true},
+ {"ab", "a", "", "b", true},
+ {"ab", "b", "", "a", true},
+ {"ab", "ab", "", "", true},
+ {"ab", "ab", "cd", "cd", true},
+ {"bc", "abc", "efg", "bc", false},
+
+ {"abc", "ab", "bc", "bcc", true},
+ {"abc", "ab", "", "c", true},
+ {"abc", "bc", "", "a", true},
+ {"abc", "bc", "d", "ad", true},
+ {"abc", "a", "123", "123bc", true},
+ {"abc", "ab", "a", "ac", true},
+ {"abc", "a", "aab", "aabbc", true},
+ {"abc", "abcd", "efg", "abc", false},
+ })));
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/value_table_test.cpp b/test/opt/value_table_test.cpp
new file mode 100644
index 0000000..ef338ae
--- /dev/null
+++ b/test/opt/value_table_test.cpp
@@ -0,0 +1,591 @@
+// 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 "source/opt/build_module.h"
+#include "source/opt/value_number_table.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using ::testing::HasSubstr;
+using ::testing::MatchesRegex;
+using ValueTableTest = PassTest<::testing::Test>;
+
+TEST_F(ValueTableTest, SameInstructionSameValue) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_EQ(vtable.GetValueNumber(inst), vtable.GetValueNumber(inst));
+}
+
+TEST_F(ValueTableTest, DifferentInstructionSameValue) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+ %11 = OpFAdd %5 %9 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(10);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(11);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, SameValueDifferentBlock) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+ OpBranch %11
+ %11 = OpLabel
+ %12 = OpFAdd %5 %9 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(10);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(12);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentValue) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+ %11 = OpFAdd %5 %9 %10
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(10);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(11);
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentValueDifferentBlock) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpFAdd %5 %9 %9
+ OpBranch %11
+ %11 = OpLabel
+ %12 = OpFAdd %5 %9 %10
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(10);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(12);
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, SameLoad) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst = context->get_def_use_mgr()->GetDef(9);
+ EXPECT_EQ(vtable.GetValueNumber(inst), vtable.GetValueNumber(inst));
+}
+
+// Two different loads, even from the same memory, must given different value
+// numbers if the memory is not read-only.
+TEST_F(ValueTableTest, DifferentFunctionLoad) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(9);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentUniformLoad) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Uniform %5
+ %8 = OpVariable %6 Uniform
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %9 = OpLoad %5 %8
+ %10 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(9);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentInputLoad) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Input %5
+ %8 = OpVariable %6 Input
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %9 = OpLoad %5 %8
+ %10 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(9);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentUniformConstantLoad) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer UniformConstant %5
+ %8 = OpVariable %6 UniformConstant
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %9 = OpLoad %5 %8
+ %10 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(9);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, DifferentPushConstantLoad) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer PushConstant %5
+ %8 = OpVariable %6 PushConstant
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %9 = OpLoad %5 %8
+ %10 = OpLoad %5 %8
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(9);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, SameCall) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypeFunction %5
+ %7 = OpTypePointer Function %5
+ %8 = OpVariable %7 Private
+ %2 = OpFunction %3 None %4
+ %9 = OpLabel
+ %10 = OpFunctionCall %5 %11
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %5 None %6
+ %12 = OpLabel
+ %13 = OpLoad %5 %8
+ OpReturnValue %13
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_EQ(vtable.GetValueNumber(inst), vtable.GetValueNumber(inst));
+}
+
+// Function calls should be given a new value number, even if they are the same.
+TEST_F(ValueTableTest, DifferentCall) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypeFunction %5
+ %7 = OpTypePointer Function %5
+ %8 = OpVariable %7 Private
+ %2 = OpFunction %3 None %4
+ %9 = OpLabel
+ %10 = OpFunctionCall %5 %11
+ %12 = OpFunctionCall %5 %11
+ OpReturn
+ OpFunctionEnd
+ %11 = OpFunction %5 None %6
+ %13 = OpLabel
+ %14 = OpLoad %5 %8
+ OpReturnValue %14
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(10);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(12);
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+// It is possible to have two instruction that compute the same numerical value,
+// but with different types. They should have different value numbers.
+TEST_F(ValueTableTest, DifferentTypes) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 0
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %8 = OpLabel
+ %9 = OpVariable %7 Function
+ %10 = OpLoad %5 %9
+ %11 = OpIAdd %5 %10 %10
+ %12 = OpIAdd %6 %10 %10
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(11);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(12);
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+TEST_F(ValueTableTest, CopyObject) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Function %5
+ %2 = OpFunction %3 None %4
+ %7 = OpLabel
+ %8 = OpVariable %6 Function
+ %9 = OpLoad %5 %8
+ %10 = OpCopyObject %5 %9
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(9);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(10);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+}
+
+// Test that a phi where the operands have the same value assigned that value
+// to the result of the phi.
+TEST_F(ValueTableTest, PhiTest1) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Uniform %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %9 = OpVariable %6 Uniform
+ %2 = OpFunction %3 None %4
+ %10 = OpLabel
+ OpBranchConditional %8 %11 %12
+ %11 = OpLabel
+ %13 = OpLoad %5 %9
+ OpBranch %14
+ %12 = OpLabel
+ %15 = OpLoad %5 %9
+ OpBranch %14
+ %14 = OpLabel
+ %16 = OpPhi %5 %13 %11 %15 %12
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(13);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(15);
+ Instruction* phi = context->get_def_use_mgr()->GetDef(16);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi));
+}
+
+// When the values for the inputs to a phi do not match, then the phi should
+// have its own value number.
+TEST_F(ValueTableTest, PhiTest2) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Uniform %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %9 = OpVariable %6 Uniform
+ %10 = OpVariable %6 Uniform
+ %2 = OpFunction %3 None %4
+ %11 = OpLabel
+ OpBranchConditional %8 %12 %13
+ %12 = OpLabel
+ %14 = OpLoad %5 %9
+ OpBranch %15
+ %13 = OpLabel
+ %16 = OpLoad %5 %10
+ OpBranch %15
+ %15 = OpLabel
+ %17 = OpPhi %14 %12 %16 %13
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(14);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(16);
+ Instruction* phi = context->get_def_use_mgr()->GetDef(17);
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi));
+ EXPECT_NE(vtable.GetValueNumber(inst2), vtable.GetValueNumber(phi));
+}
+
+// Test that a phi node in a loop header gets a new value because one of its
+// inputs comes from later in the loop.
+TEST_F(ValueTableTest, PhiLoopTest) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource GLSL 430
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeFloat 32
+ %6 = OpTypePointer Uniform %5
+ %7 = OpTypeBool
+ %8 = OpConstantTrue %7
+ %9 = OpVariable %6 Uniform
+ %10 = OpVariable %6 Uniform
+ %2 = OpFunction %3 None %4
+ %11 = OpLabel
+ %12 = OpLoad %5 %9
+ OpSelectionMerge %13 None
+ OpBranchConditional %8 %14 %13
+ %14 = OpLabel
+ %15 = OpPhi %5 %12 %11 %16 %14
+ %16 = OpLoad %5 %9
+ OpLoopMerge %17 %14 None
+ OpBranchConditional %8 %14 %17
+ %17 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ %18 = OpPhi %5 %12 %11 %16 %17
+ OpReturn
+ OpFunctionEnd
+ )";
+ auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+ ValueNumberTable vtable(context.get());
+ Instruction* inst1 = context->get_def_use_mgr()->GetDef(12);
+ Instruction* inst2 = context->get_def_use_mgr()->GetDef(16);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2));
+
+ Instruction* phi1 = context->get_def_use_mgr()->GetDef(15);
+ EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi1));
+
+ Instruction* phi2 = context->get_def_use_mgr()->GetDef(18);
+ EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi2));
+ EXPECT_NE(vtable.GetValueNumber(phi1), vtable.GetValueNumber(phi2));
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/vector_dce_test.cpp b/test/opt/vector_dce_test.cpp
new file mode 100644
index 0000000..a978a07
--- /dev/null
+++ b/test/opt/vector_dce_test.cpp
@@ -0,0 +1,1231 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 VectorDCETest = PassTest<::testing::Test>;
+
+TEST_F(VectorDCETest, InsertAfterInsertElim) {
+ // With two insertions to the same offset, the first is dead.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in float In0;
+ // layout (location=1) in float In1;
+ // layout (location=2) in vec2 In2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec2 v = In2;
+ // v.x = In0 + In1; // dead
+ // v.x = 0.0;
+ // OutColor = v.xyxy;
+ // }
+
+ const std::string before_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In2 "In2"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %OutColor "OutColor"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_n"
+OpName %_ ""
+OpDecorate %In2 Location 2
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %OutColor Location 0
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%In2 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In0 = OpVariable %_ptr_Input_float Input
+%In1 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%int = OpTypeInt 32 1
+%_Globals_ = OpTypeStruct %uint %int
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+)";
+
+ const std::string after_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In2 %In0 %In1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In2 "In2"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %OutColor "OutColor"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_n"
+OpName %_ ""
+OpDecorate %In2 Location 2
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %OutColor Location 0
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%In2 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In0 = OpVariable %_ptr_Input_float Input
+%In1 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%int = OpTypeInt 32 1
+%_Globals_ = OpTypeStruct %uint %int
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %11
+%25 = OpLabel
+%26 = OpLoad %v2float %In2
+%27 = OpLoad %float %In0
+%28 = OpLoad %float %In1
+%29 = OpFAdd %float %27 %28
+%35 = OpCompositeInsert %v2float %29 %26 0
+%37 = OpCompositeInsert %v2float %float_0 %35 0
+%33 = OpVectorShuffle %v4float %37 %37 0 1 0 1
+OpStore %OutColor %33
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%23 = OpLabel
+%24 = OpLoad %v2float %In2
+%25 = OpLoad %float %In0
+%26 = OpLoad %float %In1
+%27 = OpFAdd %float %25 %26
+%28 = OpCompositeInsert %v2float %27 %24 0
+%29 = OpCompositeInsert %v2float %float_0 %24 0
+%30 = OpVectorShuffle %v4float %29 %29 0 1 0 1
+OpStore %OutColor %30
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<VectorDCE>(before_predefs + before,
+ after_predefs + after, true, true);
+}
+
+TEST_F(VectorDCETest, DeadInsertInChainWithPhi) {
+ // Dead insert eliminated with phi in insertion chain.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in vec4 In0;
+ // layout (location=1) in float In1;
+ // layout (location=2) in float In2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // layout(std140, binding = 0 ) uniform _Globals_
+ // {
+ // bool g_b;
+ // };
+ //
+ // void main()
+ // {
+ // vec4 v = In0;
+ // v.z = In1 + In2;
+ // if (g_b) v.w = 1.0;
+ // OutColor = vec4(v.x,v.y,0.0,v.w);
+ // }
+
+ const std::string before_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%_Globals_ = OpTypeStruct %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+)";
+
+ const std::string after_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_ptr_Function_float = OpTypePointer Function %float
+%_Globals_ = OpTypeStruct %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%float_0 = OpConstant %float 0
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %11
+%31 = OpLabel
+%32 = OpLoad %v4float %In0
+%33 = OpLoad %float %In1
+%34 = OpLoad %float %In2
+%35 = OpFAdd %float %33 %34
+%51 = OpCompositeInsert %v4float %35 %32 2
+%37 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%38 = OpLoad %uint %37
+%39 = OpINotEqual %bool %38 %uint_0
+OpSelectionMerge %40 None
+OpBranchConditional %39 %41 %40
+%41 = OpLabel
+%53 = OpCompositeInsert %v4float %float_1 %51 3
+OpBranch %40
+%40 = OpLabel
+%60 = OpPhi %v4float %51 %31 %53 %41
+%55 = OpCompositeExtract %float %60 0
+%57 = OpCompositeExtract %float %60 1
+%59 = OpCompositeExtract %float %60 3
+%49 = OpCompositeConstruct %v4float %55 %57 %float_0 %59
+OpStore %OutColor %49
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%27 = OpLabel
+%28 = OpLoad %v4float %In0
+%29 = OpLoad %float %In1
+%30 = OpLoad %float %In2
+%31 = OpFAdd %float %29 %30
+%32 = OpCompositeInsert %v4float %31 %28 2
+%33 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%34 = OpLoad %uint %33
+%35 = OpINotEqual %bool %34 %uint_0
+OpSelectionMerge %36 None
+OpBranchConditional %35 %37 %36
+%37 = OpLabel
+%38 = OpCompositeInsert %v4float %float_1 %28 3
+OpBranch %36
+%36 = OpLabel
+%39 = OpPhi %v4float %28 %27 %38 %37
+%40 = OpCompositeExtract %float %39 0
+%41 = OpCompositeExtract %float %39 1
+%42 = OpCompositeExtract %float %39 3
+%43 = OpCompositeConstruct %v4float %40 %41 %float_0 %42
+OpStore %OutColor %43
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<VectorDCE>(before_predefs + before,
+ after_predefs + after, true, true);
+}
+
+TEST_F(VectorDCETest, DeadInsertWithScalars) {
+ // Dead insert which requires two passes to eliminate
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in vec4 In0;
+ // layout (location=1) in float In1;
+ // layout (location=2) in float In2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // layout(std140, binding = 0 ) uniform _Globals_
+ // {
+ // bool g_b;
+ // bool g_b2;
+ // };
+ //
+ // void main()
+ // {
+ // vec4 v1, v2;
+ // v1 = In0;
+ // v1.y = In1 + In2; // dead, second pass
+ // if (g_b) v1.x = 1.0;
+ // v2.x = v1.x;
+ // v2.y = v1.y; // dead, first pass
+ // if (g_b2) v2.x = 0.0;
+ // OutColor = vec4(v2.x,v2.x,0.0,1.0);
+ // }
+
+ const std::string before_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_b2"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_Globals_ = OpTypeStruct %uint %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%float_0 = OpConstant %float 0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%27 = OpUndef %v4float
+)";
+
+ const std::string after_predefs =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %In2 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_b"
+OpMemberName %_Globals_ 1 "g_b2"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpMemberDecorate %_Globals_ 1 Offset 4
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%uint = OpTypeInt 32 0
+%_Globals_ = OpTypeStruct %uint %uint
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%bool = OpTypeBool
+%uint_0 = OpConstant %uint 0
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%float_0 = OpConstant %float 0
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%27 = OpUndef %v4float
+%55 = OpUndef %v4float
+)";
+
+ const std::string before =
+ R"(%main = OpFunction %void None %10
+%28 = OpLabel
+%29 = OpLoad %v4float %In0
+%30 = OpLoad %float %In1
+%31 = OpLoad %float %In2
+%32 = OpFAdd %float %30 %31
+%33 = OpCompositeInsert %v4float %32 %29 1
+%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%35 = OpLoad %uint %34
+%36 = OpINotEqual %bool %35 %uint_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
+%39 = OpCompositeInsert %v4float %float_1 %33 0
+OpBranch %37
+%37 = OpLabel
+%40 = OpPhi %v4float %33 %28 %39 %38
+%41 = OpCompositeExtract %float %40 0
+%42 = OpCompositeInsert %v4float %41 %27 0
+%43 = OpCompositeExtract %float %40 1
+%44 = OpCompositeInsert %v4float %43 %42 1
+%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1
+%46 = OpLoad %uint %45
+%47 = OpINotEqual %bool %46 %uint_0
+OpSelectionMerge %48 None
+OpBranchConditional %47 %49 %48
+%49 = OpLabel
+%50 = OpCompositeInsert %v4float %float_0 %44 0
+OpBranch %48
+%48 = OpLabel
+%51 = OpPhi %v4float %44 %37 %50 %49
+%52 = OpCompositeExtract %float %51 0
+%53 = OpCompositeExtract %float %51 0
+%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1
+OpStore %OutColor %54
+OpReturn
+OpFunctionEnd
+)";
+
+ const std::string after =
+ R"(%main = OpFunction %void None %10
+%28 = OpLabel
+%29 = OpLoad %v4float %In0
+%30 = OpLoad %float %In1
+%31 = OpLoad %float %In2
+%32 = OpFAdd %float %30 %31
+%33 = OpCompositeInsert %v4float %32 %29 1
+%34 = OpAccessChain %_ptr_Uniform_uint %_ %int_0
+%35 = OpLoad %uint %34
+%36 = OpINotEqual %bool %35 %uint_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
+%39 = OpCompositeInsert %v4float %float_1 %55 0
+OpBranch %37
+%37 = OpLabel
+%40 = OpPhi %v4float %29 %28 %39 %38
+%41 = OpCompositeExtract %float %40 0
+%42 = OpCompositeInsert %v4float %41 %55 0
+%43 = OpCompositeExtract %float %40 1
+%44 = OpCompositeInsert %v4float %43 %42 1
+%45 = OpAccessChain %_ptr_Uniform_uint %_ %int_1
+%46 = OpLoad %uint %45
+%47 = OpINotEqual %bool %46 %uint_0
+OpSelectionMerge %48 None
+OpBranchConditional %47 %49 %48
+%49 = OpLabel
+%50 = OpCompositeInsert %v4float %float_0 %55 0
+OpBranch %48
+%48 = OpLabel
+%51 = OpPhi %v4float %42 %37 %50 %49
+%52 = OpCompositeExtract %float %51 0
+%53 = OpCompositeExtract %float %51 0
+%54 = OpCompositeConstruct %v4float %52 %53 %float_0 %float_1
+OpStore %OutColor %54
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<VectorDCE>(before_predefs + before,
+ after_predefs + after, true, true);
+}
+
+TEST_F(VectorDCETest, InsertObjectLive) {
+ // Make sure that the object being inserted in an OpCompositeInsert
+ // is not removed when it is uses later on.
+ const std::string before =
+ R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %In1 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%28 = OpLabel
+%29 = OpLoad %v4float %In0
+%30 = OpLoad %float %In1
+%33 = OpCompositeInsert %v4float %30 %29 1
+OpStore %OutColor %33
+OpReturn
+OpFunctionEnd
+)";
+
+ SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+ SinglePassRunAndCheck<VectorDCE>(before, before, true, true);
+}
+
+TEST_F(VectorDCETest, DeadInsertInCycle) {
+ // Dead insert in chain with cycle. Demonstrates analysis can handle
+ // cycles in chains going through scalars intermediate values.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) in vec4 In0;
+ // layout (location=1) in float In1;
+ // layout (location=2) in float In2;
+ // layout (location=0) out vec4 OutColor;
+ //
+ // layout(std140, binding = 0 ) uniform _Globals_
+ // {
+ // int g_n ;
+ // };
+ //
+ // void main()
+ // {
+ // vec2 v = vec2(0.0, 1.0);
+ // for (int i = 0; i < g_n; i++) {
+ // v.x = v.x + 1;
+ // v.y = v.y * 0.9; // dead
+ // }
+ // OutColor = vec4(v.x);
+ // }
+
+ const std::string assembly =
+ R"(
+; CHECK: [[init_val:%\w+]] = OpConstantComposite %v2float %float_0 %float_1
+; CHECK: [[undef:%\w+]] = OpUndef %v2float
+; CHECK: OpFunction
+; CHECK: [[entry_lab:%\w+]] = OpLabel
+; CHECK: [[loop_header:%\w+]] = OpLabel
+; CHECK: OpPhi %v2float [[init_val]] [[entry_lab]] [[x_insert:%\w+]] {{%\w+}}
+; CHECK: [[x_insert:%\w+]] = OpCompositeInsert %v2float %43 [[undef]] 0
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %OutColor %In0 %In1 %In2
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %_Globals_ "_Globals_"
+OpMemberName %_Globals_ 0 "g_n"
+OpName %_ ""
+OpName %OutColor "OutColor"
+OpName %In0 "In0"
+OpName %In1 "In1"
+OpName %In2 "In2"
+OpMemberDecorate %_Globals_ 0 Offset 0
+OpDecorate %_Globals_ Block
+OpDecorate %_ DescriptorSet 0
+OpDecorate %_ Binding 0
+OpDecorate %OutColor Location 0
+OpDecorate %In0 Location 0
+OpDecorate %In1 Location 1
+OpDecorate %In2 Location 2
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%16 = OpConstantComposite %v2float %float_0 %float_1
+%int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+%int_0 = OpConstant %int 0
+%_Globals_ = OpTypeStruct %int
+%_ptr_Uniform__Globals_ = OpTypePointer Uniform %_Globals_
+%_ = OpVariable %_ptr_Uniform__Globals_ Uniform
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+%bool = OpTypeBool
+%float_0_75 = OpConstant %float 0.75
+%int_1 = OpConstant %int 1
+%v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%_ptr_Input_float = OpTypePointer Input %float
+%In1 = OpVariable %_ptr_Input_float Input
+%In2 = OpVariable %_ptr_Input_float Input
+%main = OpFunction %void None %10
+%29 = OpLabel
+OpBranch %30
+%30 = OpLabel
+%31 = OpPhi %v2float %16 %29 %32 %33
+%34 = OpPhi %int %int_0 %29 %35 %33
+OpLoopMerge %36 %33 None
+OpBranch %37
+%37 = OpLabel
+%38 = OpAccessChain %_ptr_Uniform_int %_ %int_0
+%39 = OpLoad %int %38
+%40 = OpSLessThan %bool %34 %39
+OpBranchConditional %40 %41 %36
+%41 = OpLabel
+%42 = OpCompositeExtract %float %31 0
+%43 = OpFAdd %float %42 %float_1
+%44 = OpCompositeInsert %v2float %43 %31 0
+%45 = OpCompositeExtract %float %44 1
+%46 = OpFMul %float %45 %float_0_75
+%32 = OpCompositeInsert %v2float %46 %44 1
+OpBranch %33
+%33 = OpLabel
+%35 = OpIAdd %int %34 %int_1
+OpBranch %30
+%36 = OpLabel
+%47 = OpCompositeExtract %float %31 0
+%48 = OpCompositeConstruct %v4float %47 %47 %47 %47
+OpStore %OutColor %48
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<VectorDCE>(assembly, true);
+}
+
+TEST_F(VectorDCETest, DeadLoadFeedingCompositeConstruct) {
+ // Detach the loads feeding the CompositeConstruct for the unused elements.
+ // TODO: Implement the rewrite for CompositeConstruct.
+
+ const std::string assembly =
+ R"(
+; CHECK: [[undef:%\w+]] = OpUndef %float
+; CHECK: [[ac:%\w+]] = OpAccessChain %_ptr_Input_float %In0 %uint_2
+; CHECK: [[load:%\w+]] = OpLoad %float [[ac]]
+; CHECK: OpCompositeConstruct %v3float [[load]] [[undef]] [[undef]]
+OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %In0 %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+OpSourceExtension "GL_GOOGLE_include_directive"
+OpName %main "main"
+OpName %In0 "In0"
+OpName %OutColor "OutColor"
+OpDecorate %In0 Location 0
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%In0 = OpVariable %_ptr_Input_v4float Input
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+%uint_1 = OpConstant %uint 1
+%uint_2 = OpConstant %uint 2
+%v3float = OpTypeVector %float 3
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%int_20 = OpConstant %int 20
+%bool = OpTypeBool
+%float_1 = OpConstant %float 1
+%int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+%23 = OpUndef %v3float
+%main = OpFunction %void None %6
+%24 = OpLabel
+%25 = OpAccessChain %_ptr_Input_float %In0 %uint_0
+%26 = OpLoad %float %25
+%27 = OpAccessChain %_ptr_Input_float %In0 %uint_1
+%28 = OpLoad %float %27
+%29 = OpAccessChain %_ptr_Input_float %In0 %uint_2
+%30 = OpLoad %float %29
+%31 = OpCompositeConstruct %v3float %30 %28 %26
+OpBranch %32
+%32 = OpLabel
+%33 = OpPhi %v3float %31 %24 %34 %35
+%36 = OpPhi %int %int_0 %24 %37 %35
+OpLoopMerge %38 %35 None
+OpBranch %39
+%39 = OpLabel
+%40 = OpSLessThan %bool %36 %int_20
+OpBranchConditional %40 %41 %38
+%41 = OpLabel
+%42 = OpCompositeExtract %float %33 0
+%43 = OpFAdd %float %42 %float_1
+%34 = OpCompositeInsert %v3float %43 %33 0
+OpBranch %35
+%35 = OpLabel
+%37 = OpIAdd %int %36 %int_1
+OpBranch %32
+%38 = OpLabel
+%44 = OpCompositeExtract %float %33 0
+%45 = OpCompositeConstruct %v4float %44 %44 %44 %44
+OpStore %OutColor %45
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<VectorDCE>(assembly, true);
+}
+
+TEST_F(VectorDCETest, DeadLoadFeedingVectorShuffle) {
+ // Detach the loads feeding the CompositeConstruct for the unused elements.
+ // TODO: Implement the rewrite for CompositeConstruct.
+
+ const std::string assembly =
+ R"(
+; MemPass Type2Undef does not reuse and already existing undef.
+; CHECK: {{%\w+}} = OpUndef %v3float
+; CHECK: [[undef:%\w+]] = OpUndef %v3float
+; CHECK: OpFunction
+; CHECK: OpVectorShuffle %v3float {{%\w+}} [[undef]] 0 4 5
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %In0 %OutColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+ OpSourceExtension "GL_GOOGLE_include_directive"
+ OpName %main "main"
+ OpName %In0 "In0"
+ OpName %OutColor "OutColor"
+ OpDecorate %In0 Location 0
+ OpDecorate %OutColor Location 0
+ %void = OpTypeVoid
+ %6 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %In0 = OpVariable %_ptr_Input_v4float Input
+ %uint = OpTypeInt 32 0
+ %uint_0 = OpConstant %uint 0
+%_ptr_Input_float = OpTypePointer Input %float
+ %uint_1 = OpConstant %uint 1
+ %uint_2 = OpConstant %uint 2
+ %v3float = OpTypeVector %float 3
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_20 = OpConstant %int 20
+ %bool = OpTypeBool
+ %float_1 = OpConstant %float 1
+ %vec_const = OpConstantComposite %v3float %float_1 %float_1 %float_1
+ %int_1 = OpConstant %int 1
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %OutColor = OpVariable %_ptr_Output_v4float Output
+ %23 = OpUndef %v3float
+ %main = OpFunction %void None %6
+ %24 = OpLabel
+ %25 = OpAccessChain %_ptr_Input_float %In0 %uint_0
+ %26 = OpLoad %float %25
+ %27 = OpAccessChain %_ptr_Input_float %In0 %uint_1
+ %28 = OpLoad %float %27
+ %29 = OpAccessChain %_ptr_Input_float %In0 %uint_2
+ %30 = OpLoad %float %29
+ %31 = OpCompositeConstruct %v3float %30 %28 %26
+ %sh = OpVectorShuffle %v3float %vec_const %31 0 4 5
+ OpBranch %32
+ %32 = OpLabel
+ %33 = OpPhi %v3float %sh %24 %34 %35
+ %36 = OpPhi %int %int_0 %24 %37 %35
+ OpLoopMerge %38 %35 None
+ OpBranch %39
+ %39 = OpLabel
+ %40 = OpSLessThan %bool %36 %int_20
+ OpBranchConditional %40 %41 %38
+ %41 = OpLabel
+ %42 = OpCompositeExtract %float %33 0
+ %43 = OpFAdd %float %42 %float_1
+ %34 = OpCompositeInsert %v3float %43 %33 0
+ OpBranch %35
+ %35 = OpLabel
+ %37 = OpIAdd %int %36 %int_1
+ OpBranch %32
+ %38 = OpLabel
+ %44 = OpCompositeExtract %float %33 0
+ %45 = OpCompositeConstruct %v4float %44 %44 %44 %44
+ OpStore %OutColor %45
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<VectorDCE>(assembly, true);
+}
+
+TEST_F(VectorDCETest, DeadInstThroughShuffle) {
+ // Dead insert in chain with cycle. Demonstrates analysis can handle
+ // cycles in chains.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec2 v;
+ // v.x = 0.0;
+ // v.y = 0.1; // dead
+ // for (int i = 0; i < 20; i++) {
+ // v.x = v.x + 1;
+ // v = v * 0.9;
+ // }
+ // OutColor = vec4(v.x);
+ // }
+
+ const std::string assembly =
+ R"(
+; CHECK: OpFunction
+; CHECK-NOT: OpCompositeInsert %v2float {{%\w+}} 1
+; CHECK: OpFunctionEnd
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %OutColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+ OpSourceExtension "GL_GOOGLE_include_directive"
+ OpName %main "main"
+ OpName %OutColor "OutColor"
+ OpDecorate %OutColor Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v2float = OpTypeVector %float 2
+ %float_0 = OpConstant %float 0
+%float_0_100000001 = OpConstant %float 0.100000001
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_20 = OpConstant %int 20
+ %bool = OpTypeBool
+ %float_1 = OpConstant %float 1
+%float_0_899999976 = OpConstant %float 0.899999976
+ %int_1 = OpConstant %int 1
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %OutColor = OpVariable %_ptr_Output_v4float Output
+ %58 = OpUndef %v2float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %49 = OpCompositeInsert %v2float %float_0 %58 0
+ %51 = OpCompositeInsert %v2float %float_0_100000001 %49 1
+ OpBranch %22
+ %22 = OpLabel
+ %60 = OpPhi %v2float %51 %5 %38 %25
+ %59 = OpPhi %int %int_0 %5 %41 %25
+ OpLoopMerge %24 %25 None
+ OpBranch %26
+ %26 = OpLabel
+ %30 = OpSLessThan %bool %59 %int_20
+ OpBranchConditional %30 %23 %24
+ %23 = OpLabel
+ %53 = OpCompositeExtract %float %60 0
+ %34 = OpFAdd %float %53 %float_1
+ %55 = OpCompositeInsert %v2float %34 %60 0
+ %38 = OpVectorTimesScalar %v2float %55 %float_0_899999976
+ OpBranch %25
+ %25 = OpLabel
+ %41 = OpIAdd %int %59 %int_1
+ OpBranch %22
+ %24 = OpLabel
+ %57 = OpCompositeExtract %float %60 0
+ %47 = OpCompositeConstruct %v4float %57 %57 %57 %57
+ OpStore %OutColor %47
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<VectorDCE>(assembly, true);
+}
+
+TEST_F(VectorDCETest, DeadInsertThroughOtherInst) {
+ // Dead insert in chain with cycle. Demonstrates analysis can handle
+ // cycles in chains.
+ //
+ // Note: The SPIR-V assembly has had store/load elimination
+ // performed to allow the inserts and extracts to directly
+ // reference each other.
+ //
+ // #version 450
+ //
+ // layout (location=0) out vec4 OutColor;
+ //
+ // void main()
+ // {
+ // vec2 v;
+ // v.x = 0.0;
+ // v.y = 0.1; // dead
+ // for (int i = 0; i < 20; i++) {
+ // v.x = v.x + 1;
+ // v = v * 0.9;
+ // }
+ // OutColor = vec4(v.x);
+ // }
+
+ const std::string assembly =
+ R"(
+; CHECK: OpFunction
+; CHECK-NOT: OpCompositeInsert %v2float {{%\w+}} 1
+; CHECK: OpFunctionEnd
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main" %OutColor
+ OpExecutionMode %main OriginUpperLeft
+ OpSource GLSL 450
+ OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
+ OpSourceExtension "GL_GOOGLE_include_directive"
+ OpName %main "main"
+ OpName %OutColor "OutColor"
+ OpDecorate %OutColor Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v2float = OpTypeVector %float 2
+ %float_0 = OpConstant %float 0
+%float_0_100000001 = OpConstant %float 0.100000001
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+ %int_20 = OpConstant %int 20
+ %bool = OpTypeBool
+ %float_1 = OpConstant %float 1
+%float_0_899999976 = OpConstant %float 0.899999976
+ %int_1 = OpConstant %int 1
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %OutColor = OpVariable %_ptr_Output_v4float Output
+ %58 = OpUndef %v2float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %49 = OpCompositeInsert %v2float %float_0 %58 0
+ %51 = OpCompositeInsert %v2float %float_0_100000001 %49 1
+ OpBranch %22
+ %22 = OpLabel
+ %60 = OpPhi %v2float %51 %5 %38 %25
+ %59 = OpPhi %int %int_0 %5 %41 %25
+ OpLoopMerge %24 %25 None
+ OpBranch %26
+ %26 = OpLabel
+ %30 = OpSLessThan %bool %59 %int_20
+ OpBranchConditional %30 %23 %24
+ %23 = OpLabel
+ %53 = OpCompositeExtract %float %60 0
+ %34 = OpFAdd %float %53 %float_1
+ %55 = OpCompositeInsert %v2float %34 %60 0
+ %38 = OpVectorTimesScalar %v2float %55 %float_0_899999976
+ OpBranch %25
+ %25 = OpLabel
+ %41 = OpIAdd %int %59 %int_1
+ OpBranch %22
+ %24 = OpLabel
+ %57 = OpCompositeExtract %float %60 0
+ %47 = OpCompositeConstruct %v4float %57 %57 %57 %57
+ OpStore %OutColor %47
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<VectorDCE>(assembly, true);
+}
+
+TEST_F(VectorDCETest, VectorIntoCompositeConstruct) {
+ const std::string text = R"(OpCapability Linkage
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %1 "EntryPoint_Main" %2 %3
+OpExecutionMode %1 OriginUpperLeft
+OpDecorate %2 Location 0
+OpDecorate %_struct_4 Block
+OpDecorate %3 Location 0
+%float = OpTypeFloat 32
+%v2float = OpTypeVector %float 2
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%mat4v4float = OpTypeMatrix %v4float 4
+%_ptr_Function_mat4v4float = OpTypePointer Function %mat4v4float
+%v3float = OpTypeVector %float 3
+%_ptr_Function_v3float = OpTypePointer Function %v3float
+%_struct_14 = OpTypeStruct %v2float %mat4v4float %v3float %v2float %v4float
+%_ptr_Function__struct_14 = OpTypePointer Function %_struct_14
+%void = OpTypeVoid
+%int = OpTypeInt 32 1
+%int_2 = OpConstant %int 2
+%int_1 = OpConstant %int 1
+%int_4 = OpConstant %int 4
+%int_0 = OpConstant %int 0
+%int_3 = OpConstant %int 3
+%float_0 = OpConstant %float 0
+%float_1 = OpConstant %float 1
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%2 = OpVariable %_ptr_Input_v2float Input
+%_ptr_Output_v2float = OpTypePointer Output %v2float
+%_struct_4 = OpTypeStruct %v2float
+%_ptr_Output__struct_4 = OpTypePointer Output %_struct_4
+%3 = OpVariable %_ptr_Output__struct_4 Output
+%28 = OpTypeFunction %void
+%29 = OpConstantComposite %v2float %float_0 %float_0
+%30 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%31 = OpConstantComposite %mat4v4float %30 %30 %30 %30
+%32 = OpConstantComposite %v3float %float_0 %float_0 %float_0
+%1 = OpFunction %void None %28
+%33 = OpLabel
+%34 = OpVariable %_ptr_Function_v4float Function
+%35 = OpVariable %_ptr_Function__struct_14 Function
+%36 = OpAccessChain %_ptr_Function_v2float %35 %int_0
+OpStore %36 %29
+%37 = OpAccessChain %_ptr_Function_mat4v4float %35 %int_1
+OpStore %37 %31
+%38 = OpAccessChain %_ptr_Function_v3float %35 %int_2
+OpStore %38 %32
+%39 = OpAccessChain %_ptr_Function_v2float %35 %int_3
+OpStore %39 %29
+%40 = OpAccessChain %_ptr_Function_v4float %35 %int_4
+OpStore %40 %30
+%41 = OpLoad %v2float %2
+OpStore %36 %41
+%42 = OpLoad %v3float %38
+%43 = OpCompositeConstruct %v4float %42 %float_1
+%44 = OpLoad %mat4v4float %37
+%45 = OpVectorTimesMatrix %v4float %43 %44
+OpStore %34 %45
+OpCopyMemory %40 %34
+OpCopyMemory %36 %39
+%46 = OpAccessChain %_ptr_Output_v2float %3 %int_0
+%47 = OpLoad %v2float %36
+OpStore %46 %47
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndCheck<VectorDCE>(text, text, true, true);
+}
+
+TEST_F(VectorDCETest, InsertWithNoIndices) {
+ const std::string text = R"(
+; CHECK: OpEntryPoint Fragment {{%\w+}} "PSMain" [[in1:%\w+]] [[in2:%\w+]] [[out:%\w+]]
+; CHECK: OpFunction
+; CHECK: [[ld:%\w+]] = OpLoad %v4float [[in2]]
+; CHECK: OpStore [[out]] [[ld]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "PSMain" %2 %14 %3
+ OpExecutionMode %1 OriginUpperLeft
+ %void = OpTypeVoid
+ %5 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %2 = OpVariable %_ptr_Input_v4float Input
+ %14 = OpVariable %_ptr_Input_v4float Input
+ %3 = OpVariable %_ptr_Output_v4float Output
+ %1 = OpFunction %void None %5
+ %10 = OpLabel
+ %13 = OpLoad %v4float %14
+ %11 = OpLoad %v4float %2
+ %12 = OpCompositeInsert %v4float %13 %11
+ OpStore %3 %12
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<VectorDCE>(text, true);
+}
+
+TEST_F(VectorDCETest, ExtractWithNoIndices) {
+ const std::string text = R"(
+; CHECK: OpLoad %float
+; CHECK: [[ld:%\w+]] = OpLoad %v4float
+; CHECK: [[ex1:%\w+]] = OpCompositeExtract %v4float [[ld]]
+; CHECK: [[ex2:%\w+]] = OpCompositeExtract %float [[ex1]] 1
+; CHECK: OpStore {{%\w+}} [[ex2]]
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %1 "PSMain" %2 %14 %3
+ OpExecutionMode %1 OriginUpperLeft
+ %void = OpTypeVoid
+ %5 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Input_float = OpTypePointer Input %float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%_ptr_Output_float = OpTypePointer Output %float
+ %2 = OpVariable %_ptr_Input_v4float Input
+ %14 = OpVariable %_ptr_Input_float Input
+ %3 = OpVariable %_ptr_Output_float Output
+ %1 = OpFunction %void None %5
+ %10 = OpLabel
+ %13 = OpLoad %float %14
+ %11 = OpLoad %v4float %2
+ %12 = OpCompositeInsert %v4float %13 %11 0
+ %20 = OpCompositeExtract %v4float %12
+ %21 = OpCompositeExtract %float %20 1
+ OpStore %3 %21
+ OpReturn
+ OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<VectorDCE>(text, true);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/workaround1209_test.cpp b/test/opt/workaround1209_test.cpp
new file mode 100644
index 0000000..50d3c09
--- /dev/null
+++ b/test/opt/workaround1209_test.cpp
@@ -0,0 +1,423 @@
+// 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 <algorithm>
+#include <cstdarg>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <unordered_set>
+
+#include "gmock/gmock.h"
+#include "test/opt/assembly_builder.h"
+#include "test/opt/pass_fixture.h"
+#include "test/opt/pass_utils.h"
+
+namespace spvtools {
+namespace opt {
+namespace {
+
+using Workaround1209Test = PassTest<::testing::Test>;
+
+TEST_F(Workaround1209Test, RemoveOpUnreachableInLoop) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %texcoord %gl_VertexIndex %_
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpName %texcoord "texcoord"
+ OpName %buf "buf"
+ OpMemberName %buf 0 "MVP"
+ OpMemberName %buf 1 "position"
+ OpMemberName %buf 2 "attr"
+ OpName %ubuf "ubuf"
+ OpName %gl_VertexIndex "gl_VertexIndex"
+ OpName %gl_PerVertex "gl_PerVertex"
+ OpMemberName %gl_PerVertex 0 "gl_Position"
+ OpName %_ ""
+ OpDecorate %texcoord Location 0
+ OpDecorate %_arr_v4float_uint_72 ArrayStride 16
+ OpDecorate %_arr_v4float_uint_72_0 ArrayStride 16
+ OpMemberDecorate %buf 0 ColMajor
+ OpMemberDecorate %buf 0 Offset 0
+ OpMemberDecorate %buf 0 MatrixStride 16
+ OpMemberDecorate %buf 1 Offset 64
+ OpMemberDecorate %buf 2 Offset 1216
+ OpDecorate %buf Block
+ OpDecorate %ubuf DescriptorSet 0
+ OpDecorate %ubuf Binding 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+ OpDecorate %gl_PerVertex Block
+ %void = OpTypeVoid
+ %12 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %texcoord = OpVariable %_ptr_Output_v4float Output
+%mat4v4float = OpTypeMatrix %v4float 4
+ %uint = OpTypeInt 32 0
+ %uint_72 = OpConstant %uint 72
+%_arr_v4float_uint_72 = OpTypeArray %v4float %uint_72
+%_arr_v4float_uint_72_0 = OpTypeArray %v4float %uint_72
+ %buf = OpTypeStruct %mat4v4float %_arr_v4float_uint_72 %_arr_v4float_uint_72_0
+%_ptr_Uniform_buf = OpTypePointer Uniform %buf
+ %ubuf = OpVariable %_ptr_Uniform_buf Uniform
+ %int = OpTypeInt 32 1
+ %int_2 = OpConstant %int 2
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+ %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %float_1 = OpConstant %float 1
+ %28 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+ %main = OpFunction %void None %12
+ %29 = OpLabel
+ OpBranch %30
+ %30 = OpLabel
+; CHECK: OpLoopMerge [[merge:%[a-zA-Z_\d]+]]
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge:%[a-zA-Z_\d]+]]
+ OpSelectionMerge %34 None
+ OpSwitch %int_1 %35
+ %35 = OpLabel
+ %36 = OpLoad %int %gl_VertexIndex
+ %37 = OpAccessChain %_ptr_Uniform_v4float %ubuf %int_2 %36
+ %38 = OpLoad %v4float %37
+ OpStore %texcoord %38
+ %39 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %39 %28
+ OpBranch %31
+; CHECK: [[sel_merge]] = OpLabel
+ %34 = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+ OpUnreachable
+ %32 = OpLabel
+ OpBranch %30
+ %31 = OpLabel
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<Workaround1209>(text, false);
+}
+
+TEST_F(Workaround1209Test, RemoveOpUnreachableInNestedLoop) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %2 "main" %3 %4 %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %2 "main"
+ OpName %3 "texcoord"
+ OpName %6 "buf"
+ OpMemberName %6 0 "MVP"
+ OpMemberName %6 1 "position"
+ OpMemberName %6 2 "attr"
+ OpName %7 "ubuf"
+ OpName %4 "gl_VertexIndex"
+ OpName %8 "gl_PerVertex"
+ OpMemberName %8 0 "gl_Position"
+ OpName %5 ""
+ OpDecorate %3 Location 0
+ OpDecorate %9 ArrayStride 16
+ OpDecorate %10 ArrayStride 16
+ OpMemberDecorate %6 0 ColMajor
+ OpMemberDecorate %6 0 Offset 0
+ OpMemberDecorate %6 0 MatrixStride 16
+ OpMemberDecorate %6 1 Offset 64
+ OpMemberDecorate %6 2 Offset 1216
+ OpDecorate %6 Block
+ OpDecorate %7 DescriptorSet 0
+ OpDecorate %7 Binding 0
+ OpDecorate %4 BuiltIn VertexIndex
+ OpMemberDecorate %8 0 BuiltIn Position
+ OpDecorate %8 Block
+ %11 = OpTypeVoid
+ %12 = OpTypeFunction %11
+ %13 = OpTypeFloat 32
+ %14 = OpTypeVector %13 4
+ %15 = OpTypePointer Output %14
+ %3 = OpVariable %15 Output
+ %16 = OpTypeMatrix %14 4
+ %17 = OpTypeInt 32 0
+ %18 = OpConstant %17 72
+ %9 = OpTypeArray %14 %18
+ %10 = OpTypeArray %14 %18
+ %6 = OpTypeStruct %16 %9 %10
+ %19 = OpTypePointer Uniform %6
+ %7 = OpVariable %19 Uniform
+ %20 = OpTypeInt 32 1
+ %21 = OpConstant %20 2
+ %22 = OpTypePointer Input %20
+ %4 = OpVariable %22 Input
+ %23 = OpTypePointer Uniform %14
+ %8 = OpTypeStruct %14
+ %24 = OpTypePointer Output %8
+ %5 = OpVariable %24 Output
+ %25 = OpConstant %20 0
+ %26 = OpConstant %20 1
+ %27 = OpConstant %13 1
+ %28 = OpConstantComposite %14 %27 %27 %27 %27
+ %2 = OpFunction %11 None %12
+ %29 = OpLabel
+ OpBranch %31
+ %31 = OpLabel
+; CHECK: OpLoopMerge
+ OpLoopMerge %32 %33 None
+ OpBranch %30
+ %30 = OpLabel
+; CHECK: OpLoopMerge [[merge:%[a-zA-Z_\d]+]]
+ OpLoopMerge %34 %35 None
+ OpBranch %36
+ %36 = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge:%[a-zA-Z_\d]+]]
+ OpSelectionMerge %37 None
+ OpSwitch %26 %38
+ %38 = OpLabel
+ %39 = OpLoad %20 %4
+ %40 = OpAccessChain %23 %7 %21 %39
+ %41 = OpLoad %14 %40
+ OpStore %3 %41
+ %42 = OpAccessChain %15 %5 %25
+ OpStore %42 %28
+ OpBranch %34
+; CHECK: [[sel_merge]] = OpLabel
+ %37 = OpLabel
+; CHECK-NEXT: OpBranch [[merge]]
+ OpUnreachable
+ %35 = OpLabel
+ OpBranch %30
+ %34 = OpLabel
+ OpBranch %32
+ %33 = OpLabel
+ OpBranch %31
+ %32 = OpLabel
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<Workaround1209>(text, false);
+}
+
+TEST_F(Workaround1209Test, RemoveOpUnreachableInAdjacentLoops) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %2 "main" %3 %4 %5
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %2 "main"
+ OpName %3 "texcoord"
+ OpName %6 "buf"
+ OpMemberName %6 0 "MVP"
+ OpMemberName %6 1 "position"
+ OpMemberName %6 2 "attr"
+ OpName %7 "ubuf"
+ OpName %4 "gl_VertexIndex"
+ OpName %8 "gl_PerVertex"
+ OpMemberName %8 0 "gl_Position"
+ OpName %5 ""
+ OpDecorate %3 Location 0
+ OpDecorate %9 ArrayStride 16
+ OpDecorate %10 ArrayStride 16
+ OpMemberDecorate %6 0 ColMajor
+ OpMemberDecorate %6 0 Offset 0
+ OpMemberDecorate %6 0 MatrixStride 16
+ OpMemberDecorate %6 1 Offset 64
+ OpMemberDecorate %6 2 Offset 1216
+ OpDecorate %6 Block
+ OpDecorate %7 DescriptorSet 0
+ OpDecorate %7 Binding 0
+ OpDecorate %4 BuiltIn VertexIndex
+ OpMemberDecorate %8 0 BuiltIn Position
+ OpDecorate %8 Block
+ %11 = OpTypeVoid
+ %12 = OpTypeFunction %11
+ %13 = OpTypeFloat 32
+ %14 = OpTypeVector %13 4
+ %15 = OpTypePointer Output %14
+ %3 = OpVariable %15 Output
+ %16 = OpTypeMatrix %14 4
+ %17 = OpTypeInt 32 0
+ %18 = OpConstant %17 72
+ %9 = OpTypeArray %14 %18
+ %10 = OpTypeArray %14 %18
+ %6 = OpTypeStruct %16 %9 %10
+ %19 = OpTypePointer Uniform %6
+ %7 = OpVariable %19 Uniform
+ %20 = OpTypeInt 32 1
+ %21 = OpConstant %20 2
+ %22 = OpTypePointer Input %20
+ %4 = OpVariable %22 Input
+ %23 = OpTypePointer Uniform %14
+ %8 = OpTypeStruct %14
+ %24 = OpTypePointer Output %8
+ %5 = OpVariable %24 Output
+ %25 = OpConstant %20 0
+ %26 = OpConstant %20 1
+ %27 = OpConstant %13 1
+ %28 = OpConstantComposite %14 %27 %27 %27 %27
+ %2 = OpFunction %11 None %12
+ %29 = OpLabel
+ OpBranch %30
+ %30 = OpLabel
+; CHECK: OpLoopMerge [[merge1:%[a-zA-Z_\d]+]]
+ OpLoopMerge %31 %32 None
+ OpBranch %33
+ %33 = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge1:%[a-zA-Z_\d]+]]
+ OpSelectionMerge %34 None
+ OpSwitch %26 %35
+ %35 = OpLabel
+ %36 = OpLoad %20 %4
+ %37 = OpAccessChain %23 %7 %21 %36
+ %38 = OpLoad %14 %37
+ OpStore %3 %38
+ %39 = OpAccessChain %15 %5 %25
+ OpStore %39 %28
+ OpBranch %31
+; CHECK: [[sel_merge1]] = OpLabel
+ %34 = OpLabel
+; CHECK-NEXT: OpBranch [[merge1]]
+ OpUnreachable
+ %32 = OpLabel
+ OpBranch %30
+ %31 = OpLabel
+; CHECK: OpLoopMerge [[merge2:%[a-zA-Z_\d]+]]
+ OpLoopMerge %40 %41 None
+ OpBranch %42
+ %42 = OpLabel
+; CHECK: OpSelectionMerge [[sel_merge2:%[a-zA-Z_\d]+]]
+ OpSelectionMerge %43 None
+ OpSwitch %26 %44
+ %44 = OpLabel
+ %45 = OpLoad %20 %4
+ %46 = OpAccessChain %23 %7 %21 %45
+ %47 = OpLoad %14 %46
+ OpStore %3 %47
+ %48 = OpAccessChain %15 %5 %25
+ OpStore %48 %28
+ OpBranch %40
+; CHECK: [[sel_merge2]] = OpLabel
+ %43 = OpLabel
+; CHECK-NEXT: OpBranch [[merge2]]
+ OpUnreachable
+ %41 = OpLabel
+ OpBranch %31
+ %40 = OpLabel
+ OpReturn
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<Workaround1209>(text, false);
+}
+
+TEST_F(Workaround1209Test, LeaveUnreachableNotInLoop) {
+ const std::string text = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %texcoord %gl_VertexIndex %_
+ OpSource GLSL 400
+ OpSourceExtension "GL_ARB_separate_shader_objects"
+ OpSourceExtension "GL_ARB_shading_language_420pack"
+ OpName %main "main"
+ OpName %texcoord "texcoord"
+ OpName %buf "buf"
+ OpMemberName %buf 0 "MVP"
+ OpMemberName %buf 1 "position"
+ OpMemberName %buf 2 "attr"
+ OpName %ubuf "ubuf"
+ OpName %gl_VertexIndex "gl_VertexIndex"
+ OpName %gl_PerVertex "gl_PerVertex"
+ OpMemberName %gl_PerVertex 0 "gl_Position"
+ OpName %_ ""
+ OpDecorate %texcoord Location 0
+ OpDecorate %_arr_v4float_uint_72 ArrayStride 16
+ OpDecorate %_arr_v4float_uint_72_0 ArrayStride 16
+ OpMemberDecorate %buf 0 ColMajor
+ OpMemberDecorate %buf 0 Offset 0
+ OpMemberDecorate %buf 0 MatrixStride 16
+ OpMemberDecorate %buf 1 Offset 64
+ OpMemberDecorate %buf 2 Offset 1216
+ OpDecorate %buf Block
+ OpDecorate %ubuf DescriptorSet 0
+ OpDecorate %ubuf Binding 0
+ OpDecorate %gl_VertexIndex BuiltIn VertexIndex
+ OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+ OpDecorate %gl_PerVertex Block
+ %void = OpTypeVoid
+ %12 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %texcoord = OpVariable %_ptr_Output_v4float Output
+%mat4v4float = OpTypeMatrix %v4float 4
+ %uint = OpTypeInt 32 0
+ %uint_72 = OpConstant %uint 72
+%_arr_v4float_uint_72 = OpTypeArray %v4float %uint_72
+%_arr_v4float_uint_72_0 = OpTypeArray %v4float %uint_72
+ %buf = OpTypeStruct %mat4v4float %_arr_v4float_uint_72 %_arr_v4float_uint_72_0
+%_ptr_Uniform_buf = OpTypePointer Uniform %buf
+ %ubuf = OpVariable %_ptr_Uniform_buf Uniform
+ %int = OpTypeInt 32 1
+ %int_2 = OpConstant %int 2
+%_ptr_Input_int = OpTypePointer Input %int
+%gl_VertexIndex = OpVariable %_ptr_Input_int Input
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%gl_PerVertex = OpTypeStruct %v4float
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+ %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+ %int_0 = OpConstant %int 0
+ %int_1 = OpConstant %int 1
+ %float_1 = OpConstant %float 1
+ %28 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+ %main = OpFunction %void None %12
+ %29 = OpLabel
+ OpBranch %30
+ %30 = OpLabel
+ OpSelectionMerge %34 None
+ OpSwitch %int_1 %35
+ %35 = OpLabel
+ %36 = OpLoad %int %gl_VertexIndex
+ %37 = OpAccessChain %_ptr_Uniform_v4float %ubuf %int_2 %36
+ %38 = OpLoad %v4float %37
+ OpStore %texcoord %38
+ %39 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %39 %28
+ OpReturn
+ %34 = OpLabel
+; CHECK: OpUnreachable
+ OpUnreachable
+ OpFunctionEnd)";
+
+ SinglePassRunAndMatch<Workaround1209>(text, false);
+}
+
+} // namespace
+} // namespace opt
+} // namespace spvtools