| // Copyright (c) 2025 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 <ostream> |
| |
| #include "spirv-tools/optimizer.hpp" |
| #include "test/opt/pass_fixture.h" |
| #include "test/opt/pass_utils.h" |
| |
| namespace spvtools { |
| namespace opt { |
| namespace { |
| |
| struct ResolveBindingConflictsTest : public PassTest<::testing::Test> { |
| virtual void SetUp() override { |
| SetTargetEnv(SPV_ENV_VULKAN_1_1); // allow storage buffer storage class |
| // without extension. |
| SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); |
| SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | |
| SPV_BINARY_TO_TEXT_OPTION_INDENT | |
| SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); |
| } |
| }; |
| |
| using StringList = std::vector<std::string>; |
| std::string EntryPointDecls(const StringList& names) { |
| std::ostringstream os; |
| for (auto& name : names) { |
| os << " OpEntryPoint GLCompute %" + name + " \"" + name + |
| "\"\n"; |
| } |
| for (auto& name : names) { |
| os << " OpExecutionMode %" + name + " LocalSize 1 1 1\n"; |
| } |
| for (auto& name : names) { |
| os << " OpName %" + name + " \"" + name + "\"\n"; |
| } |
| return os.str(); |
| } |
| |
| std::string Preamble(const StringList& names = {"main"}) { |
| return R"( OpCapability Shader |
| OpMemoryModel Logical GLSL450 |
| )" + EntryPointDecls(names) + |
| R"( OpName %voidfn "voidfn" |
| OpName %s_ty "s_ty" |
| OpName %i_ty "i_ty" |
| OpName %si_ty "si_ty" |
| OpName %p_s_ty "p_s_ty" |
| OpName %p_i_ty "p_i_ty" |
| OpName %p_si_ty "p_si_ty" |
| OpName %st_ty "st_ty" |
| OpName %pu_st_ty "pu_st_ty" |
| OpName %pb_st_ty "pb_st_ty" |
| )"; |
| } |
| |
| std::string BasicTypes() { |
| return R"( OpDecorate %st_ty Block |
| OpMemberDecorate %st_ty 0 Offset 0 |
| %float = OpTypeFloat 32 |
| %uint = OpTypeInt 32 0 |
| %uint_0 = OpConstant %uint 0 |
| %uint_3 = OpConstant %uint 3 |
| %void = OpTypeVoid |
| %voidfn = OpTypeFunction %void |
| %s_ty = OpTypeSampler |
| %i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown |
| %si_ty = OpTypeSampledImage %i_ty |
| %p_i_ty = OpTypePointer UniformConstant %i_ty |
| %p_s_ty = OpTypePointer UniformConstant %s_ty |
| %p_si_ty = OpTypePointer UniformConstant %si_ty |
| %st_ty = OpTypeStruct %uint |
| %pu_st_ty = OpTypePointer Uniform %st_ty |
| %pb_st_ty = OpTypePointer StorageBuffer %st_ty |
| )"; |
| } |
| std::string NoCheck() { return "; CHECK-NOT: nothing to see"; } |
| |
| TEST_F(ResolveBindingConflictsTest, NoBindings_NoChange) { |
| const std::string kTest = Preamble() + BasicTypes() + |
| R"( %main = OpFunction %void None %voidfn |
| %100 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest + NoCheck(), /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithoutChange); |
| EXPECT_EQ(kTest, disasm); |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, NoConflict_UnusedVars_NoChange) { |
| const std::string kTest = Preamble() + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 0 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 0 |
| OpDecorate %104 DescriptorSet 0 |
| OpDecorate %104 Binding 0 |
| |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 0 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 0 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 0 |
| ; CHECK: OpDecorate %104 DescriptorSet 0 |
| ; CHECK: OpDecorate %104 Binding 0 |
| |
| )" + BasicTypes() + R"( |
| |
| ; Unused variables |
| |
| %100 = OpVariable %p_i_ty UniformConstant ; image |
| %101 = OpVariable %p_s_ty UniformConstant ; sampler |
| %102 = OpVariable %pu_st_ty Uniform ; UBO |
| %103 = OpVariable %pb_st_ty StorageBuffer ; SSBO |
| %104 = OpVariable %p_si_ty UniformConstant ; combined sampled image |
| |
| %main = OpFunction %void None %voidfn |
| %10 = OpLabel |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, NoConflict_UsedVars_NoChange) { |
| const std::string kTest = Preamble() + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 1 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 2 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 3 |
| OpDecorate %110 DescriptorSet 1 |
| OpDecorate %110 Binding 0 |
| |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 0 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 1 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 3 |
| ; CHECK: OpDecorate %110 DescriptorSet 1 |
| ; CHECK: OpDecorate %110 Binding 0 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_i_ty UniformConstant ; image |
| %101 = OpVariable %p_s_ty UniformConstant ; sampler |
| %102 = OpVariable %pu_st_ty Uniform ; UBO |
| %103 = OpVariable %pb_st_ty StorageBuffer ; SSBO |
| %110 = OpVariable %p_si_ty UniformConstant ; combined sampled image |
| |
| %main = OpFunction %void None %voidfn |
| %10 = OpLabel |
| %11 = OpCopyObject %p_i_ty %100 |
| %12 = OpCopyObject %p_s_ty %101 |
| %13 = OpCopyObject %pu_st_ty %102 |
| %14 = OpCopyObject %pb_st_ty %103 |
| %15 = OpCopyObject %p_si_ty %110 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| OneEntryPoint_SamplerFirstConflict_Resolves) { |
| const std::string kTest = Preamble() + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| |
| ; The sampler's binding number is incremented, even when listed first. |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant ; sampler listed first |
| %101 = OpVariable %p_i_ty UniformConstant |
| |
| %main = OpFunction %void None %voidfn |
| %10 = OpLabel |
| %11 = OpCopyObject %p_s_ty %100 |
| %12 = OpCopyObject %p_i_ty %101 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| OneEntryPoint_SamplerSecondConflict_Resolves) { |
| const std::string kTest = Preamble() + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| |
| ; The sampler's binding number is incremented, even when listed second. |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 0 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 1 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_i_ty UniformConstant |
| %101 = OpVariable %p_s_ty UniformConstant ; sampler listed second |
| |
| %main = OpFunction %void None %voidfn |
| %10 = OpLabel |
| %11 = OpCopyObject %p_i_ty %100 |
| %12 = OpCopyObject %p_s_ty %101 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, OneEntryPoint_Conflict_Ripples) { |
| const std::string kTest = Preamble() + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 1 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 2 |
| OpDecorate %104 DescriptorSet 0 |
| OpDecorate %104 Binding 3 |
| |
| ; The sampler's binding number is incremented, and later |
| ; bindings move out of the way. |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 3 |
| ; CHECK: OpDecorate %104 DescriptorSet 0 |
| ; CHECK: OpDecorate %104 Binding 4 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant ; sampler comes first |
| %101 = OpVariable %p_i_ty UniformConstant |
| %102 = OpVariable %pu_st_ty Uniform |
| %103 = OpVariable %pb_st_ty StorageBuffer |
| %104 = OpVariable %p_si_ty UniformConstant |
| |
| %main = OpFunction %void None %voidfn |
| %10 = OpLabel |
| %11 = OpCopyObject %p_s_ty %100 |
| %12 = OpCopyObject %p_i_ty %101 |
| %13 = OpCopyObject %pu_st_ty %102 |
| %14 = OpCopyObject %pb_st_ty %103 |
| %15 = OpCopyObject %p_si_ty %104 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| OneEntryPoint_Conflict_RippleStopsAtFirstHole) { |
| const std::string kTest = Preamble() + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 1 |
| ; Leave a hole at (0, 2) |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 3 |
| OpDecorate %104 DescriptorSet 0 |
| OpDecorate %104 Binding 4 |
| |
| ; There was a hole at binding 2. The ripple stops there. |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 3 |
| ; CHECK: OpDecorate %104 DescriptorSet 0 |
| ; CHECK: OpDecorate %104 Binding 4 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant ; sampler comes first |
| %101 = OpVariable %p_i_ty UniformConstant |
| %102 = OpVariable %pu_st_ty Uniform |
| %103 = OpVariable %pb_st_ty StorageBuffer |
| %104 = OpVariable %p_si_ty UniformConstant |
| |
| %main = OpFunction %void None %voidfn |
| %10 = OpLabel |
| %11 = OpCopyObject %p_s_ty %100 |
| %12 = OpCopyObject %p_i_ty %101 |
| %13 = OpCopyObject %pu_st_ty %102 |
| %14 = OpCopyObject %pb_st_ty %103 |
| %15 = OpCopyObject %p_si_ty %104 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, OneEntryPoint_MultiConflict_Resolves) { |
| const std::string kTest = Preamble() + |
| R"( |
| ; Two conflicts: at Bindings 0, and 1 |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 1 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 1 |
| OpDecorate %104 DescriptorSet 0 |
| OpDecorate %104 Binding 2 |
| |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 3 |
| ; CHECK: OpDecorate %104 DescriptorSet 0 |
| ; CHECK: OpDecorate %104 Binding 4 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant ; sampler first |
| %101 = OpVariable %p_i_ty UniformConstant |
| %102 = OpVariable %p_i_ty UniformConstant |
| %103 = OpVariable %p_s_ty UniformConstant ; sampler second |
| %104 = OpVariable %pu_st_ty Uniform |
| |
| %main = OpFunction %void None %voidfn |
| %10 = OpLabel |
| %11 = OpCopyObject %p_s_ty %100 |
| %12 = OpCopyObject %p_i_ty %101 |
| %13 = OpCopyObject %p_i_ty %102 |
| %14 = OpCopyObject %p_s_ty %103 |
| %15 = OpCopyObject %pu_st_ty %104 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| OneEntryPoint_MultiConflict_ComplexCallGraph_Resolves) { |
| // Check that uses are seen even when used at various points in a complex call |
| // graph. |
| const std::string kTest = Preamble() + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 1 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 1 |
| OpDecorate %104 DescriptorSet 0 |
| OpDecorate %104 Binding 2 |
| |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 3 |
| ; CHECK: OpDecorate %104 DescriptorSet 0 |
| ; CHECK: OpDecorate %104 Binding 4 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant ; used in %200 |
| %101 = OpVariable %p_i_ty UniformConstant ; used in %300, %400 |
| %102 = OpVariable %p_i_ty UniformConstant ; used in %500 |
| %103 = OpVariable %p_s_ty UniformConstant ; used in %400 twice |
| %104 = OpVariable %pu_st_ty Uniform ; used in %600 |
| |
| %200 = OpFunction %void None %voidfn |
| %201 = OpLabel |
| %202 = OpCopyObject %p_s_ty %100 |
| OpReturn |
| OpFunctionEnd |
| |
| %300 = OpFunction %void None %voidfn |
| %301 = OpLabel |
| %302 = OpCopyObject %p_i_ty %101 |
| OpReturn |
| OpFunctionEnd |
| |
| %400 = OpFunction %void None %voidfn |
| %401 = OpLabel |
| %402 = OpFunctionCall %void %200 |
| %403 = OpCopyObject %p_s_ty %103 |
| %404 = OpCopyObject %p_i_ty %101 |
| %405 = OpCopyObject %p_s_ty %103 |
| %406 = OpFunctionCall %void %300 |
| OpReturn |
| OpFunctionEnd |
| |
| %500 = OpFunction %void None %voidfn |
| %501 = OpLabel |
| %502 = OpFunctionCall %void %400 |
| %503 = OpCopyObject %p_i_ty %102 |
| %504 = OpFunctionCall %void %300 |
| OpReturn |
| OpFunctionEnd |
| |
| %600 = OpFunction %void None %voidfn |
| %601 = OpLabel |
| %602 = OpFunctionCall %void %300 |
| %603 = OpFunctionCall %void %500 |
| %604 = OpCopyObject %pu_st_ty %104 |
| OpReturn |
| OpFunctionEnd |
| |
| %main = OpFunction %void None %voidfn |
| %1000 = OpLabel |
| %1001 = OpFunctionCall %void %600 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| MultiEntryPoint_DuplicatConflicts_ResolvesOnlyOnce) { |
| // Before: |
| // |
| // Binding: 0 1 |
| // Alpha: %100,%101 |
| // Beta: %100,%101 |
| // |
| // After: |
| // |
| // Binding: 0 1 |
| // Alpha: %101 %100 |
| // Beta: %101 %100 |
| const std::string kTest = Preamble({"alpha", "beta"}) + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant |
| %101 = OpVariable %p_i_ty UniformConstant |
| |
| %alpha = OpFunction %void None %voidfn |
| %1000 = OpLabel |
| %1001 = OpCopyObject %p_s_ty %100 |
| %1002 = OpCopyObject %p_i_ty %101 |
| OpReturn |
| OpFunctionEnd |
| |
| %beta = OpFunction %void None %voidfn |
| %2000 = OpLabel |
| %2001 = OpCopyObject %p_s_ty %100 |
| %2002 = OpCopyObject %p_i_ty %101 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| MultiEntryPoint_IndependentConflicts_Resolves) { |
| // Before: |
| // |
| // Binding: 0 1 |
| // Alpha: %100,%101 |
| // Beta: %102,%103 |
| // |
| // After: |
| // |
| // Binding: 0 1 |
| // Alpha: %101 %100 |
| // Beta: %102 %103 |
| const std::string kTest = Preamble({"alpha", "beta"}) + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 0 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 0 |
| |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 0 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 1 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant |
| %101 = OpVariable %p_i_ty UniformConstant |
| %102 = OpVariable %p_i_ty UniformConstant |
| %103 = OpVariable %p_s_ty UniformConstant |
| |
| %alpha = OpFunction %void None %voidfn |
| %1000 = OpLabel |
| %1001 = OpCopyObject %p_s_ty %100 |
| %1002 = OpCopyObject %p_i_ty %101 |
| OpReturn |
| OpFunctionEnd |
| |
| %beta = OpFunction %void None %voidfn |
| %2000 = OpLabel |
| %2001 = OpCopyObject %p_i_ty %102 |
| %2002 = OpCopyObject %p_s_ty %103 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| MultiEntryPoint_SameVarConflictsAcrossMultiEntryPoints_Resolves) { |
| // A sampler variable is bumped, causing potential conflicts in other shaders. |
| // |
| // Before: |
| // |
| // Binding: 0 1 2 |
| // Alpha: %100,%101 |
| // Beta: %100 %102 |
| // Gamma: %100 %103 |
| // |
| // After: |
| // |
| // Binding: 0 1 2 |
| // Alpha: %101 %100 |
| // Beta: %100 %102 |
| // Gamma: %100 %103 |
| // |
| const std::string kTest = Preamble({"alpha", "beta", "gamma"}) + |
| R"( |
| OpDecorate %100 DescriptorSet 0 ; The sampler |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 1 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 2 |
| |
| ; bumped once |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| |
| ; pushed back from bump of %100 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| |
| ; does not need to be bumped |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 2 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant ; used in alpha, beta, gamma |
| %101 = OpVariable %p_i_ty UniformConstant ; used in alpha |
| %102 = OpVariable %pu_st_ty Uniform ; used in beta |
| %103 = OpVariable %pb_st_ty StorageBuffer ; used in gamma |
| |
| %alpha = OpFunction %void None %voidfn |
| %1000 = OpLabel |
| %1001 = OpCopyObject %p_s_ty %100 |
| %1002 = OpCopyObject %p_i_ty %101 |
| OpReturn |
| OpFunctionEnd |
| |
| %beta = OpFunction %void None %voidfn |
| %2000 = OpLabel |
| %2001 = OpCopyObject %p_s_ty %100 |
| %2002 = OpCopyObject %pu_st_ty %102 |
| OpReturn |
| OpFunctionEnd |
| |
| %gamma = OpFunction %void None %voidfn |
| %3000 = OpLabel |
| %3001 = OpCopyObject %p_s_ty %100 |
| %3002 = OpCopyObject %pb_st_ty %103 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, MultiEntryPoint_ConflictCascade_Resolves) { |
| // Before: |
| // |
| // Binding: 0 1 2 3 |
| // Alpha: %100,%101 |
| // Beta: %100 %102 |
| // Gamma: %102 %103 |
| // Delta: %103 %104 |
| // |
| // After: |
| // |
| // Binding: 0 1 2 3 4 |
| // Alpha: %101 %100 |
| // Beta: %100 %102 |
| // Gamma: %102 %103 |
| // Delta: %103 %104 |
| // |
| const std::string kTest = Preamble({"alpha", "beta", "gamma", "delta"}) + |
| R"( |
| OpDecorate %100 DescriptorSet 0 ; The sampler |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 1 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 2 |
| OpDecorate %104 DescriptorSet 0 |
| OpDecorate %104 Binding 3 |
| |
| ; %100 is bumped once: |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 1 |
| |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 0 |
| |
| ; pushed back from bump of %100 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| |
| ; pushed back from bump of %102 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 3 |
| |
| ; pushed back from bump of %103 |
| ; CHECK: OpDecorate %104 DescriptorSet 0 |
| ; CHECK: OpDecorate %104 Binding 4 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_s_ty UniformConstant ; used in alpha, beta |
| %101 = OpVariable %p_i_ty UniformConstant ; used in alpha |
| %102 = OpVariable %pu_st_ty Uniform ; used in beta, gamma |
| %103 = OpVariable %pb_st_ty StorageBuffer ; used in gamma, delta |
| %104 = OpVariable %p_si_ty UniformConstant ; used delta |
| |
| %alpha = OpFunction %void None %voidfn |
| %1000 = OpLabel |
| %1001 = OpCopyObject %p_s_ty %100 |
| %1002 = OpCopyObject %p_i_ty %101 |
| OpReturn |
| OpFunctionEnd |
| |
| %beta = OpFunction %void None %voidfn |
| %2000 = OpLabel |
| %2001 = OpCopyObject %p_s_ty %100 |
| %2002 = OpCopyObject %pu_st_ty %102 |
| OpReturn |
| OpFunctionEnd |
| |
| %gamma = OpFunction %void None %voidfn |
| %3000 = OpLabel |
| %3001 = OpCopyObject %pu_st_ty %102 |
| %3002 = OpCopyObject %pb_st_ty %103 |
| OpReturn |
| OpFunctionEnd |
| |
| %delta = OpFunction %void None %voidfn |
| %4000 = OpLabel |
| %4001 = OpCopyObject %pb_st_ty %103 |
| %4002 = OpCopyObject %p_si_ty %104 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| TEST_F(ResolveBindingConflictsTest, |
| MultiEntryPoint_ConflictCascade_RevisitEntryPoint) { |
| // Prove that the settling algorithm knows to revisit entry points that |
| // already had all their own conflicts resolved. |
| // |
| // Before: |
| // |
| // Binding: 0 1 2 3 4 |
| // Alpha: %100,%101 %103 %104 %105 |
| // Beta: %101 %102 %103 %105 |
| // |
| // After: |
| // |
| // Binding: 0 1 2 3 4 5 |
| // Alpha: %100 %101 %103 %104 %105 |
| // Beta: %101 %102 %103 %105 |
| const std::string kTest = Preamble({"alpha", "beta"}) + |
| R"( |
| OpDecorate %100 DescriptorSet 0 |
| OpDecorate %100 Binding 0 |
| OpDecorate %101 DescriptorSet 0 ; the sampler |
| OpDecorate %101 Binding 0 |
| OpDecorate %102 DescriptorSet 0 |
| OpDecorate %102 Binding 1 |
| OpDecorate %103 DescriptorSet 0 |
| OpDecorate %103 Binding 2 |
| OpDecorate %104 DescriptorSet 0 |
| OpDecorate %104 Binding 3 |
| OpDecorate %105 DescriptorSet 0 |
| OpDecorate %105 Binding 4 |
| |
| ; CHECK: OpDecorate %100 DescriptorSet 0 |
| ; CHECK: OpDecorate %100 Binding 0 |
| ; CHECK: OpDecorate %101 DescriptorSet 0 |
| ; CHECK: OpDecorate %101 Binding 1 |
| ; CHECK: OpDecorate %102 DescriptorSet 0 |
| ; CHECK: OpDecorate %102 Binding 2 |
| ; CHECK: OpDecorate %103 DescriptorSet 0 |
| ; CHECK: OpDecorate %103 Binding 3 |
| ; CHECK: OpDecorate %104 DescriptorSet 0 |
| ; CHECK: OpDecorate %104 Binding 4 |
| ; CHECK: OpDecorate %105 DescriptorSet 0 |
| ; CHECK: OpDecorate %105 Binding 5 |
| |
| )" + BasicTypes() + R"( |
| |
| %100 = OpVariable %p_i_ty UniformConstant |
| %101 = OpVariable %p_s_ty UniformConstant |
| %102 = OpVariable %pu_st_ty Uniform |
| %103 = OpVariable %pb_st_ty StorageBuffer |
| %104 = OpVariable %p_si_ty UniformConstant |
| %105 = OpVariable %p_s_ty UniformConstant |
| |
| %alpha = OpFunction %void None %voidfn |
| %1000 = OpLabel |
| %1001 = OpCopyObject %p_i_ty %100 |
| %1002 = OpCopyObject %p_s_ty %101 |
| %1003 = OpCopyObject %pb_st_ty %103 |
| %1004 = OpCopyObject %p_si_ty %104 |
| %1005 = OpCopyObject %p_s_ty %105 |
| OpReturn |
| OpFunctionEnd |
| |
| %beta = OpFunction %void None %voidfn |
| %2000 = OpLabel |
| %2001 = OpCopyObject %p_s_ty %101 |
| %2002 = OpCopyObject %pu_st_ty %102 |
| %2003 = OpCopyObject %pb_st_ty %103 |
| %2004 = OpCopyObject %p_s_ty %105 |
| OpReturn |
| OpFunctionEnd |
| )"; |
| |
| auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>( |
| kTest, /* do_validation= */ true); |
| EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; |
| } |
| |
| } // namespace |
| } // namespace opt |
| } // namespace spvtools |