// 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 #include #include #include "spirv-tools/optimizer.hpp" #include "test/opt/pass_fixture.h" #include "test/opt/pass_utils.h" namespace spvtools { namespace opt { namespace { struct SplitCombinedImageSamplerPassTest : public PassTest<::testing::Test> { virtual void SetUp() override { SetTargetEnv(SPV_ENV_VULKAN_1_0); 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); } }; struct TypeCase { const char* glsl_type; const char* image_type_decl; }; std::ostream& operator<<(std::ostream& os, const TypeCase& tc) { os << tc.glsl_type; return os; } struct SplitCombinedImageSamplerPassTypeCaseTest : public PassTest<::testing::TestWithParam> { virtual void SetUp() override { SetTargetEnv(SPV_ENV_VULKAN_1_0); 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); } }; std::vector ImageTypeCases() { return std::vector{ {"sampler2D", "OpTypeImage %float 2D 0 0 0 1 Unknown"}, {"sampler2DShadow", "OpTypeImage %float 2D 1 0 0 1 Unknown"}, {"sampler2DArray", "OpTypeImage %float 2D 0 1 0 1 Unknown"}, {"sampler2DArrayShadow", "OpTypeImage %float 2D 1 1 0 1 Unknown"}, {"sampler2DMS", "OpTypeImage %float 2D 0 0 1 1 Unknown"}, {"sampler2DMSArray", "OpTypeImage %float 2D 0 1 1 1 Unknown"}, {"sampler3D", "OpTypeImage %float 3D 0 0 0 1 Unknown"}, {"samplerCube", "OpTypeImage %float Cube 0 0 0 1 Unknown"}, {"samplerCubeShadow", "OpTypeImage %float Cube 1 0 0 1 Unknown"}, {"samplerCubeArray", "OpTypeImage %float Cube 0 1 0 1 Unknown"}, {"samplerCubeArrayShadow", "OpTypeImage %float Cube 1 1 0 1 Unknown"}, {"isampler2D", "OpTypeImage %int 2D 0 0 0 1 Unknown"}, {"isampler2DShadow", "OpTypeImage %int 2D 1 0 0 1 Unknown"}, {"isampler2DArray", "OpTypeImage %int 2D 0 1 0 1 Unknown"}, {"isampler2DArrayShadow", "OpTypeImage %int 2D 1 1 0 1 Unknown"}, {"isampler2DMS", "OpTypeImage %int 2D 0 0 1 1 Unknown"}, {"isampler2DMSArray", "OpTypeImage %int 2D 0 1 1 1 Unknown"}, {"isampler3D", "OpTypeImage %int 3D 0 0 0 1 Unknown"}, {"isamplerCube", "OpTypeImage %int Cube 0 0 0 1 Unknown"}, {"isamplerCubeShadow", "OpTypeImage %int Cube 1 0 0 1 Unknown"}, {"isamplerCubeArray", "OpTypeImage %int Cube 0 1 0 1 Unknown"}, {"isamplerCubeArrayShadow", "OpTypeImage %int Cube 1 1 0 1 Unknown"}, {"usampler2D", "OpTypeImage %uint 2D 0 0 0 1 Unknown"}, {"usampler2DShadow", "OpTypeImage %uint 2D 1 0 0 1 Unknown"}, {"usampler2DArray", "OpTypeImage %uint 2D 0 1 0 1 Unknown"}, {"usampler2DArrayShadow", "OpTypeImage %uint 2D 1 1 0 1 Unknown"}, {"usampler2DMS", "OpTypeImage %uint 2D 0 0 1 1 Unknown"}, {"usampler2DMSArray", "OpTypeImage %uint 2D 0 1 1 1 Unknown"}, {"usampler3D", "OpTypeImage %uint 3D 0 0 0 1 Unknown"}, {"usamplerCube", "OpTypeImage %uint Cube 0 0 0 1 Unknown"}, {"usamplerCubeShadow", "OpTypeImage %uint Cube 1 0 0 1 Unknown"}, {"usamplerCubeArray", "OpTypeImage %uint Cube 0 1 0 1 Unknown"}, {"usamplerCubeArrayShadow", "OpTypeImage %uint Cube 1 1 0 1 Unknown"}, }; } std::string Preamble(const std::string shader_interface = "") { return R"( OpCapability Shader OpCapability RuntimeDescriptorArray OpExtension "SPV_EXT_descriptor_indexing" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %main "main")" + shader_interface + R"( OpExecutionMode %main LocalSize 1 1 1 OpName %main "main" OpName %main_0 "main_0" OpName %voidfn "voidfn" )"; } std::string PreambleFragment(const std::string shader_interface = "") { return R"( OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main")" + shader_interface + R"( OpExecutionMode %main OriginUpperLeft OpName %main "main" OpName %main_0 "main_0" OpName %voidfn "voidfn" )"; } std::string BasicTypes() { return R"( %float = OpTypeFloat 32 %uint = OpTypeInt 32 0 %int = OpTypeInt 32 1 %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %uint_3 = OpConstant %uint 3 %float_0 = OpConstant %float 0 %v2float = OpTypeVector %float 2 %v3float = OpTypeVector %float 3 %v4float = OpTypeVector %float 4 %13 = OpConstantNull %v2float %14 = OpConstantNull %v3float %15 = OpConstantNull %v4float %void = OpTypeVoid %voidfn = OpTypeFunction %void )"; } std::string Main() { return R"( %main = OpFunction %void None %voidfn %main_0 = OpLabel OpReturn OpFunctionEnd )"; } std::string NoCheck() { return "; CHECK-NOT: nothing to see"; } TEST_F(SplitCombinedImageSamplerPassTest, SamplerOnly_NoChange) { const std::string kTest = Preamble() + R"( OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 )" + BasicTypes() + R"( %10 = OpTypeSampler %_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 %100 = OpVariable %_ptr_UniformConstant_10 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %6 = OpLoad %10 %100 OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest + NoCheck(), /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << "status" << kTest << "\n -> \n" << disasm; EXPECT_EQ(disasm, kTest) << "disasm"; } TEST_F(SplitCombinedImageSamplerPassTest, ImageOnly_NoChange) { const std::string kTest = Preamble() + R"( OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown %_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 %100 = OpVariable %_ptr_UniformConstant_10 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %6 = OpLoad %10 %100 OpReturn OpFunctionEnd )"; SCOPED_TRACE("image only"); auto [disasm, status] = SinglePassRunAndMatch( kTest + NoCheck(), /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithoutChange); EXPECT_EQ(disasm, kTest); } TEST_F(SplitCombinedImageSamplerPassTest, PtrSampledImageOnly_DeletesPtrType) { const std::string kTest = Preamble() + BasicTypes() + R"( ; CHECK: OpCapability Shader ; CHECK-NOT: OpTypePointer UniformConstant ; CHECK: OpFunction %void %100 = OpTypeImage %float 2D 0 0 0 1 Unknown %101 = OpTypeSampledImage %100 %102 = OpTypePointer UniformConstant %101 %main = OpFunction %void None %voidfn %main_0 = OpLabel OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest + NoCheck(), /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << "status"; } TEST_F(SplitCombinedImageSamplerPassTest, PtrArraySampledImageOnly_DeletesPtrType) { const std::string kTest = Preamble() + BasicTypes() + R"( ; CHECK: OpCapability Shader ; CHECK-NOT: OpTypePointer UniformConstant ; CHECK: OpFunction %void %100 = OpTypeImage %float 2D 0 0 0 1 Unknown %101 = OpTypeSampledImage %100 %103 = OpTypeArray %101 %uint_1 %104 = OpTypePointer UniformConstant %103 %main = OpFunction %void None %voidfn %main_0 = OpLabel OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest + NoCheck(), /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << "status"; } TEST_F(SplitCombinedImageSamplerPassTest, PtrRtArraySampledImageOnly_DeletesPtrType) { const std::string kTest = Preamble() + BasicTypes() + R"( ; CHECK: OpCapability Shader ; CHECK-NOT: OpTypePointer UniformConstant ; CHECK: OpFunction %void %100 = OpTypeImage %float 2D 0 0 0 1 Unknown %101 = OpTypeSampledImage %100 %103 = OpTypeRuntimeArray %101 %104 = OpTypePointer UniformConstant %103 %main = OpFunction %void None %voidfn %main_0 = OpLabel OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest + NoCheck(), /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << "status"; } TEST_F(SplitCombinedImageSamplerPassTest, Combined_NoSampler_CreatedBeforeSampledImage) { // No OpTypeSampler to begin with. const std::string kTest = Preamble() + R"( OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 ; A sampler type is created and placed at the start of types. ; CHECK: OpDecorate %{{\d+}} Binding 0 ; CHECK: OpDecorate %{{\d+}} Binding 0 ; CHECK-NOT: TypeSampledImage ; CHECK: TypeSampler ; CHECK: TypeSampledImage )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %6 = OpLoad %11 %100 OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, Combined_SynthesizeVarNames) { // Also tests binding info is copied to both variables. const std::string kTest = Preamble() + R"( OpName %orig_var "orig_var" OpDecorate %orig_var DescriptorSet 0 OpDecorate %orig_var Binding 0 ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK: OpCapability ; The original name is deleted ; CHECK-NOT: OpName %orig_var " ; CHECK: OpName %orig_var_image "orig_var_image" ; CHECK: OpName %orig_var_sampler "orig_var_sampler" ; CHECK-NOT: OpName %orig_var " ; CHECK: OpDecorate %orig_var_image DescriptorSet 0 ; CHECK: OpDecorate %orig_var_sampler DescriptorSet 0 ; CHECK: OpDecorate %orig_var_image Binding 0 ; CHECK: OpDecorate %orig_var_sampler Binding 0 ; CHECK: %10 = OpTypeImage % ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; CHECK-NOT: %orig_var = OpVariable ; CHECK-DAG: %orig_var_sampler = OpVariable %[[sampler_ptr_ty]] UniformConstant ; CHECK-DAG: %orig_var_image = OpVariable %[[image_ptr_ty]] UniformConstant ; CHECK: = OpFunction )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %orig_var = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %101 = OpLoad %11 %orig_var OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, Combined_RemapLoad) { // Also tests binding info is copied to both variables. const std::string kTest = Preamble() + R"( OpName %combined "combined" OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 ; CHECK: OpName ; CHECK-NOT: OpDecorate %100 ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[image_var]] Binding 0 ; CHECK: OpDecorate %[[sampler_var]] Binding 0 ; CHECK: %10 = OpTypeImage % ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK-NOT: %100 = OpVariable ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[sampler_ptr_ty]] UniformConstant ; CHECK-DAG: %[[image_var]] = OpVariable %[[image_ptr_ty]] UniformConstant ; CHECK: = OpFunction ; The load of the combined image+sampler is replaced by a two loads, then ; a combination operation. ; CHECK: %[[im:\d+]] = OpLoad %10 %[[image_var]] ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[sampler_var]] ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] )" + BasicTypes() + " %10 = " + GetParam().image_type_decl + R"( %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %combined = OpLoad %11 %100 ; Uses of the combined image sampler are preserved. ; CHECK: OpCopyObject %11 %combined %7 = OpCopyObject %11 %combined OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, Combined_RemapLoad_RelaxedPrecisionOnVarCopied) { // All decorations on the variable are copied. In this case, RelaxedPrecision const std::string kTest = Preamble() + R"( OpName %combined "combined" OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 OpDecorate %100 RelaxedPrecision ; CHECK: OpName ; CHECK-NOT: OpDecorate %100 ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[image_var]] Binding 0 ; CHECK: OpDecorate %[[sampler_var]] Binding 0 ; CHECK: OpDecorate %[[image_var:\d+]] RelaxedPrecision ; CHECK: OpDecorate %[[sampler_var:\d+]] RelaxedPrecision ; CHECK: %10 = OpTypeImage % ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK-NOT: %100 = OpVariable ; CHECK-NOT: OpVariable _ptr_UniformConstant_11 ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[sampler_ptr_ty]] UniformConstant ; CHECK-DAG: %[[image_var]] = OpVariable %[[image_ptr_ty]] UniformConstant ; CHECK: = OpFunction ; The load of the combined image+sampler is replaced by a two loads, then ; a combination operation. ; CHECK: %[[im:\d+]] = OpLoad %10 %[[image_var]] ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[sampler_var]] ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] %bool = OpTypeBool ; location marker )" + BasicTypes() + " %10 = " + GetParam().image_type_decl + R"( %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %combined = OpLoad %11 %100 ; Uses of the combined image sampler are preserved. ; CHECK: OpCopyObject %11 %combined %7 = OpCopyObject %11 %combined OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, Combined_RemapLoad_RelaxedPrecisionOnLoadCopied) { // Copy decorations form an OpLoad that is replaced. const std::string kTest = Preamble() + R"( OpName %combined "combined" OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 OpDecorate %combined RelaxedPrecision ; CHECK: OpName ; CHECK-NOT: OpDecorate %100 ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[image_var]] Binding 0 ; CHECK: OpDecorate %[[sampler_var]] Binding 0 ; This is what we are checking in this test. ; CHECK: OpDecorate %[[im:\d+]] RelaxedPrecision ; CHECK: OpDecorate %[[s:\d+]] RelaxedPrecision ; CHECK: %10 = OpTypeImage % ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK-NOT: %100 = OpVariable ; CHECK-NOT: OpVariable _ptr_UniformConstant_11 ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[sampler_ptr_ty]] UniformConstant ; CHECK-DAG: %[[image_var]] = OpVariable %[[image_ptr_ty]] UniformConstant ; CHECK: = OpFunction ; The load of the combined image+sampler is replaced by a two loads, then ; a combination operation. The new loads get the same decorations that the ; original load had. ; CHECK: %[[im]] = OpLoad %10 %[[image_var]] ; CHECK: %[[s]] = OpLoad %[[sampler_ty]] %[[sampler_var]] ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] %bool = OpTypeBool ; location marker )" + BasicTypes() + " %10 = " + GetParam().image_type_decl + R"( %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %combined = OpLoad %11 %100 ; Uses of the combined image sampler are preserved. ; CHECK: OpCopyObject %11 %combined %7 = OpCopyObject %11 %combined OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, Combined_DeletesCopyObjectOfPtr) { // OpCopyObject is deleted, and its uses updated. const std::string kTest = Preamble() + R"( OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 ; CHECK: OpName ; CHECK-NOT: OpDecorate %100 ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[image_var]] Binding 0 ; CHECK: OpDecorate %[[sampler_var]] Binding 0 ; CHECK: %10 = OpTypeImage % ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK-NOT: %100 = OpVariable ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[sampler_ptr_ty]] UniformConstant ; CHECK-DAG: %[[image_var]] = OpVariable %[[image_ptr_ty]] UniformConstant ; CHECK: = OpFunction )" + BasicTypes() + " %10 = " + GetParam().image_type_decl + R"( %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %101 = OpCopyObject %_ptr_UniformConstant_11 %100 %102 = OpLoad %11 %101 %103 = OpCopyObject %_ptr_UniformConstant_11 %101 %104 = OpCopyObject %11 %102 ;; this copy survives OpReturn OpFunctionEnd ; The OpCopyObject instructions are removed. ; The load of the combined image+sampler is replaced by a two loads, then ; a combination operation. The only OpCopyObject that remains is the copy ; of the copy of the sampled image value. ; CHECK: %[[im:\d+]] = OpLoad %10 %[[image_var]] ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[sampler_var]] ; CHECK: %[[si:\d+]] = OpSampledImage %11 %[[im]] %[[s]] ; CHECK-NEXT: OpCopyObject %11 %[[si]] ; CHECK-NEXT: OpReturn )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, ArrayCombined_RemapLoad) { const std::string kTest = Preamble() + R"( OpName %combined "combined" OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 ; CHECK: OpName ; CHECK-NOT: OpDecorate %100 ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[image_var]] Binding 0 ; CHECK: OpDecorate %[[sampler_var]] Binding 0 ; CHECK: %10 = OpTypeImage % ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK: %[[array_image_ty:\w+]] = OpTypeArray %10 %uint_3 ; CHECK: %[[ptr_array_image_ty:\w+]] = OpTypePointer UniformConstant %[[array_image_ty]] ; CHECK: %[[array_sampler_ty:\w+]] = OpTypeArray %[[sampler_ty]] %uint_3 ; CHECK: %[[ptr_array_sampler_ty:\w+]] = OpTypePointer UniformConstant %[[array_sampler_ty]] ; CHECK-NOT: %100 = OpVariable ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[ptr_array_sampler_ty]] UniformConstant ; CHECK-DAG: %[[image_var]] = OpVariable %[[ptr_array_image_ty]] UniformConstant ; CHECK: = OpFunction ; The access chain and load is replaced by two access chains, two loads, then ; a combine operation. ; CHECK: %[[ptr_im:\d+]] = OpAccessChain %[[image_ptr_ty]] %[[image_var]] %uint_1 ; CHECK: %[[ptr_s:\d+]] = OpAccessChain %[[sampler_ptr_ty]] %[[sampler_var]] %uint_1 ; CHECK: %[[im:\d+]] = OpLoad %10 %[[ptr_im]] ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[ptr_s]] ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] )" + BasicTypes() + " %10 = " + GetParam().image_type_decl + R"( %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %12 = OpTypeArray %11 %uint_3 %_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 %100 = OpVariable %_ptr_UniformConstant_12 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %ptr = OpAccessChain %_ptr_UniformConstant_11 %100 %uint_1 %combined = OpLoad %11 %ptr ; Uses of the combined image sampler are preserved. ; CHECK: OpCopyObject %11 %combined %7 = OpCopyObject %11 %combined OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, RtArrayCombined_RemapLoad) { const std::string kTest = Preamble() + R"( OpName %combined "combined" OpDecorate %100 DescriptorSet 0 OpDecorate %100 Binding 0 ; CHECK: OpName ; CHECK-NOT: OpDecorate %100 ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 ; CHECK: OpDecorate %[[image_var]] Binding 0 ; CHECK: OpDecorate %[[sampler_var]] Binding 0 ; CHECK: %10 = OpTypeImage % ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK: %[[array_image_ty:\w+]] = OpTypeRuntimeArray %10 ; CHECK: %[[ptr_array_image_ty:\w+]] = OpTypePointer UniformConstant %[[array_image_ty]] ; CHECK: %[[array_sampler_ty:\w+]] = OpTypeRuntimeArray %[[sampler_ty]] ; CHECK: %[[ptr_array_sampler_ty:\w+]] = OpTypePointer UniformConstant %[[array_sampler_ty]] ; CHECK-NOT: %100 = OpVariable ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[ptr_array_sampler_ty]] UniformConstant ; CHECK-DAG: %[[image_var]] = OpVariable %[[ptr_array_image_ty]] UniformConstant ; CHECK: = OpFunction ; The access chain and load is replaced by two access chains, two loads, then ; a combine operation. ; CHECK: %[[ptr_im:\d+]] = OpAccessChain %[[image_ptr_ty]] %[[image_var]] %uint_1 ; CHECK: %[[ptr_s:\d+]] = OpAccessChain %[[sampler_ptr_ty]] %[[sampler_var]] %uint_1 ; CHECK: %[[im:\d+]] = OpLoad %10 %[[ptr_im]] ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[ptr_s]] ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] )" + BasicTypes() + " %10 = " + GetParam().image_type_decl + R"( %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %12 = OpTypeRuntimeArray %11 %_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 %100 = OpVariable %_ptr_UniformConstant_12 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel %ptr = OpAccessChain %_ptr_UniformConstant_11 %100 %uint_1 %combined = OpLoad %11 %ptr ; Uses of the combined image sampler are preserved. ; CHECK: OpCopyObject %11 %combined %7 = OpCopyObject %11 %combined OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } INSTANTIATE_TEST_SUITE_P(AllCombinedTypes, SplitCombinedImageSamplerPassTypeCaseTest, ::testing::ValuesIn(ImageTypeCases())); // Remap entry point struct EntryPointRemapCase { const spv_target_env environment = SPV_ENV_VULKAN_1_0; const char* initial_interface = ""; const char* expected_interface = nullptr; }; std::ostream& operator<<(std::ostream& os, const EntryPointRemapCase& eprc) { os << "(env " << spvLogStringForEnv(eprc.environment) << ", init " << eprc.initial_interface << " -> expect " << eprc.expected_interface << ")"; return os; } struct SplitCombinedImageSamplerPassEntryPointRemapTest : public PassTest<::testing::TestWithParam> { virtual void SetUp() override { SetTargetEnv(GetParam().environment); 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); } }; std::vector EntryPointInterfaceCases() { return std::vector{ {SPV_ENV_VULKAN_1_0, " %in_var %out_var", " %in_var %out_var"}, {SPV_ENV_VULKAN_1_4, " %combined_var", " %combined_var_image %combined_var_sampler"}, {SPV_ENV_VULKAN_1_4, " %combined_var %in_var %out_var", " %combined_var_image %in_var %out_var %combined_var_sampler"}, {SPV_ENV_VULKAN_1_4, " %in_var %combined_var %out_var", " %in_var %combined_var_image %out_var %combined_var_sampler"}, {SPV_ENV_VULKAN_1_4, " %in_var %out_var %combined_var", " %in_var %out_var %combined_var_image %combined_var_sampler"}, }; } TEST_P(SplitCombinedImageSamplerPassEntryPointRemapTest, EntryPoint_Combined_UsedInShader) { const std::string kTest = PreambleFragment(GetParam().initial_interface) + R"( OpName %combined "combined" OpName %combined_var "combined_var" OpName %in_var "in_var" OpName %out_var "out_var" OpDecorate %combined_var DescriptorSet 0 OpDecorate %combined_var Binding 0 OpDecorate %in_var BuiltIn FragCoord OpDecorate %out_var Location 0 ; CHECK: OpEntryPoint Fragment %main "main")" + GetParam().expected_interface + R"( ; These clauses ensure the expected interface is the whole interface. ; CHECK-NOT: %{{\d+}} ; CHECK-NOT: %in_var ; CHECK-NOT: %out_var ; CHECK-NOT: %combined_var ; CHECK: OpExecutionMode %main OriginUpperLeft ; Check the var names, tracing up through the types. ; CHECK: %10 = OpTypeImage %float 2D 0 0 0 1 Unknown ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] ; The combined image variable is replaced by an image variable and a sampler variable. ; CHECK-DAG: %combined_var_sampler = OpVariable %[[sampler_ptr_ty]] UniformConstant ; CHECK-DAG: %combined_var_image = OpVariable %[[image_ptr_ty]] UniformConstant ; CHECK: = OpFunction %bool = OpTypeBool )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %in_ptr_v4f = OpTypePointer Input %v4float %in_var = OpVariable %in_ptr_v4f Input %out_ptr_v4f = OpTypePointer Output %v4float %out_var = OpVariable %out_ptr_v4f Output %combined_var = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn ;CHECK: %main_0 = OpLabel ;CHECK: OpLoad %main_0 = OpLabel %combined = OpLoad %11 %combined_var OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassEntryPointRemapTest, EntryPoint_Combined_UsedOnlyInEntryPointInstruction) { // If the combined var is in the interface, that is enough to trigger // its replacement. Otherwise the entry point interface is untouched // when the combined var is not otherwise used. const bool combined_var_in_interface = std::string(GetParam().initial_interface).find("%combined_var") != std::string::npos; if (combined_var_in_interface) { const std::string kTest = PreambleFragment(GetParam().initial_interface) + R"( OpName %combined_var "combined_var" OpName %in_var "in_var" OpName %out_var "out_var" OpDecorate %combined_var DescriptorSet 0 OpDecorate %combined_var Binding 0 OpDecorate %in_var BuiltIn FragCoord OpDecorate %out_var Location 0 ; CHECK: OpEntryPoint Fragment %main "main")" + GetParam().expected_interface + R"( ; These clauses ensure the expected interface is the whole interface. ; CHECK-NOT: %{{\d+}} ; CHECK-NOT: %in_var ; CHECK-NOT: %out_var ; CHECK-NOT: %combined_var ; CHECK: OpExecutionMode %main OriginUpperLeft %bool = OpTypeBool )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %in_ptr_v4f = OpTypePointer Input %v4float %in_var = OpVariable %in_ptr_v4f Input %out_ptr_v4f = OpTypePointer Output %v4float %out_var = OpVariable %out_ptr_v4f Output ; %combined_var is not used! %combined_var = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } } TEST_P(SplitCombinedImageSamplerPassEntryPointRemapTest, EntryPoint_Combined_Unused) { // If the combined var is in the interface, that is enough to trigger // its replacement. Otherwise the entry point interface is untouched // when the combined var is not otherwise used. const bool combined_var_in_interface = std::string(GetParam().initial_interface).find("%combined_var") != std::string::npos; if (!combined_var_in_interface) { const std::string kTest = PreambleFragment(GetParam().initial_interface) + R"( ; CHECK: OpEntryPoint Fragment %main "main")" + GetParam().initial_interface // Note this is the // intial interface + R"( ; These clauses ensure the expected interface is the whole interface. ; CHECK-NOT: %{{\d+}} ; CHECK-NOT: %in_var ; CHECK-NOT: %out_var ; CHECK-NOT: %combined_var ; CHECK: OpExecutionMode %main OriginUpperLeft ; The variable disappears. ; CHECK-NOT: %combined_var = ; CHECK: OpFunctionEnd OpName %combined_var "combined_var" OpName %in_var "in_var" OpName %out_var "out_var" OpDecorate %combined_var DescriptorSet 0 OpDecorate %combined_var Binding 0 OpDecorate %in_var BuiltIn FragCoord OpDecorate %out_var Location 0 %bool = OpTypeBool )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown %11 = OpTypeSampledImage %10 %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 %in_ptr_v4f = OpTypePointer Input %v4float %in_var = OpVariable %in_ptr_v4f Input %out_ptr_v4f = OpTypePointer Output %v4float %out_var = OpVariable %out_ptr_v4f Output ; %combined_var is not used! %combined_var = OpVariable %_ptr_UniformConstant_11 UniformConstant %main = OpFunction %void None %voidfn %main_0 = OpLabel OpReturn OpFunctionEnd )"; auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } } INSTANTIATE_TEST_SUITE_P(EntryPointRemap, SplitCombinedImageSamplerPassEntryPointRemapTest, ::testing::ValuesIn(EntryPointInterfaceCases())); // Remap function types struct FunctionTypeCase { const char* initial_type_params = ""; const char* expected_type_params = ""; }; std::ostream& operator<<(std::ostream& os, const FunctionTypeCase& ftc) { os << "(init " << ftc.initial_type_params << " -> expect " << ftc.expected_type_params << ")"; return os; } struct SplitCombinedImageSamplerPassFunctionTypeTest : public PassTest<::testing::TestWithParam> { virtual void SetUp() override { 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); } }; std::vector FunctionTypeCases() { return std::vector{ {"", ""}, {" %image_ty", " %image_ty"}, {" %sampler_ty", " %sampler_ty"}, {" %sampled_image_ty", " %image_ty %sampler_ty"}, {" %uint %sampled_image_ty %float", " %uint %image_ty %sampler_ty %float"}, {" %ptr_sampled_image_ty", " %_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty"}, {" %uint %ptr_sampled_image_ty %float", " %uint %_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty " "%float"}, {" %uint %ptr_sampled_image_ty %ptr_sampled_image_ty %float", " %uint %_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty " "%_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty " "%float"}, }; } TEST_P(SplitCombinedImageSamplerPassFunctionTypeTest, ReplaceCombinedImageSamplersOnly) { const std::string kTest = Preamble() + +R"( OpName %f_ty "f_ty" OpName %sampler_ty "sampler_ty" OpName %image_ty "image_ty" OpName %sampled_image_ty "sampled_image_ty" OpName %ptr_sampled_image_ty "sampled_image_ty" )" + BasicTypes() + R"( %sampler_ty = OpTypeSampler %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown %sampled_image_ty = OpTypeSampledImage %image_ty %ptr_sampled_image_ty = OpTypePointer UniformConstant %sampled_image_ty %f_ty = OpTypeFunction %float)" + GetParam().initial_type_params + R"( ; CHECK: %f_ty = OpTypeFunction %float)" + GetParam().expected_type_params + R"( )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_P(SplitCombinedImageSamplerPassFunctionTypeTest, AvoidDuplicateType) { // SPIR-V does not allow duplicate non-aggregate types. That includes function // types. Test that when replacing function type parameters would cause a // collision, that the original function type is replaced with the new one. const std::string initial_params(GetParam().initial_type_params); const std::string expected_params(GetParam().expected_type_params); const std::string kTest = Preamble() + +R"( OpName %sampler_ty "sampler_ty" OpName %image_ty "image_ty" OpName %sampled_image_ty "sampled_image_ty" OpName %_ptr_UniformConstant_sampler_ty "_ptr_UniformConstant_sampler_ty" OpName %_ptr_UniformConstant_image_ty "_ptr_UniformConstant_image_ty" OpName %ptr_sampled_image_ty "sampled_image_ty" OpName %dest_ty "dest_ty" )" + BasicTypes() + R"( %sampler_ty = OpTypeSampler %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown %sampled_image_ty = OpTypeSampledImage %image_ty %ptr_sampled_image_ty = OpTypePointer UniformConstant %sampled_image_ty %_ptr_UniformConstant_image_ty = OpTypePointer UniformConstant %image_ty %_ptr_UniformConstant_sampler_ty = OpTypePointer UniformConstant %sampler_ty %100 = OpTypeFunction %float)" + initial_params + R"( %dest_ty = OpTypeFunction %float)" + expected_params + R"( ; CHECK: OpTypeSampler ; CHECK-NOT: %100 = ; CHECK: %dest_ty = OpTypeFunction %float)" + expected_params + R"( ; CHECK-NOT: %100 = ; CHECK: %main = OpFunction )" + Main(); // The original source is invalid if initial and expected params are the same, // because the type is already duplicated. // Only test when they are different. if (initial_params != expected_params) { auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } } INSTANTIATE_TEST_SUITE_P(FunctionTypeRemap, SplitCombinedImageSamplerPassFunctionTypeTest, ::testing::ValuesIn(FunctionTypeCases())); // Test array and runtime-array cases for function type replacement. TEST_F(SplitCombinedImageSamplerPassTest, FunctionType_ReplaceSampledImageArg) { // The original module has a sampled image type, used only as a function // parameter. We still want to replace it. But no other sampled-image types // exist. This proves that the pass needs a sampled_image_used_as_param_ // state variable. const std::string kTest = Preamble() + +R"( OpName %f_ty "f_ty" OpName %sampler_ty "sampler_ty" OpName %image_ty "image_ty" OpName %sampled_image_ty "sampled_image_ty" )" + BasicTypes() + R"( %sampler_ty = OpTypeSampler %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown %sampled_image_ty = OpTypeSampledImage %image_ty %f_ty = OpTypeFunction %float %sampled_image_ty %float ; CHECK: %f_ty = OpTypeFunction %float %image_ty %sampler_ty %float )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionType_ReplaceArrayArg) { const std::string kTest = Preamble() + +R"( OpName %f_ty "f_ty" OpName %sampler_ty "sampler_ty" OpName %image_ty "image_ty" OpName %sampled_image_ty "sampled_image_ty" OpName %ptr_array_si_ty "ptr_array_si_ty" )" + BasicTypes() + R"( %sampler_ty = OpTypeSampler %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown %sampled_image_ty = OpTypeSampledImage %image_ty %array_si_ty = OpTypeArray %sampled_image_ty %uint_3 %ptr_array_si_ty = OpTypePointer UniformConstant %array_si_ty ; CHECK: %[[array_i_ty:\w+]] = OpTypeArray %image_ty %uint_3 ; CHECK: %[[ptr_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] ; CHECK: %[[array_s_ty:\w+]] = OpTypeArray %sampler_ty %uint_3 ; CHECK: %[[ptr_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] %f_ty = OpTypeFunction %float %uint %ptr_array_si_ty %float ; CHECK: %f_ty = OpTypeFunction %float %uint %[[ptr_array_i_ty]] %[[ptr_array_s_ty]] %float )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionType_ReplaceRtArrayArg) { const std::string kTest = Preamble() + +R"( OpName %f_ty "f_ty" OpName %sampler_ty "sampler_ty" OpName %image_ty "image_ty" OpName %sampled_image_ty "sampled_image_ty" OpName %ptr_array_si_ty "ptr_array_si_ty" )" + BasicTypes() + R"( %sampler_ty = OpTypeSampler %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown %sampled_image_ty = OpTypeSampledImage %image_ty %array_si_ty = OpTypeRuntimeArray %sampled_image_ty %ptr_array_si_ty = OpTypePointer UniformConstant %array_si_ty ; CHECK: %[[array_i_ty:\w+]] = OpTypeRuntimeArray %image_ty ; CHECK: %[[ptr_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] ; CHECK: %[[array_s_ty:\w+]] = OpTypeRuntimeArray %sampler_ty ; CHECK: %[[ptr_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] %f_ty = OpTypeFunction %float %uint %ptr_array_si_ty %float ; CHECK: %f_ty = OpTypeFunction %float %uint %[[ptr_array_i_ty]] %[[ptr_array_s_ty]] %float )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } // Remap function bodies std::string NamedITypes() { return R"( OpName %f "f" OpName %f_ty "f_ty" OpName %i_ty "i_ty" OpName %s_ty "s_ty" OpName %p_i_ty "p_i_ty" OpName %p_s_ty "p_s_ty" )"; } std::string NamedCombinedTypes() { return R"( OpName %si_ty "si_ty" OpName %p_si_ty "p_si_ty" OpName %array_si_ty "array_si_ty" OpName %rtarray_si_ty "rtarray_si_ty" OpName %p_array_si_ty "p_array_si_ty" OpName %p_rtarray_si_ty "p_rtarray_si_ty" )"; } std::string NamedCaller() { return R"( OpName %caller_ty "caller_ty" OpName %caller "caller" OpName %caller_entry "caller_entry" OpName %caller_call "caller_call" OpName %caller_arg "caller_arg" )"; } std::string ITypes() { return R"( %i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown %s_ty = OpTypeSampler %p_i_ty = OpTypePointer UniformConstant %i_ty %p_s_ty = OpTypePointer UniformConstant %s_ty )"; } std::string CombinedTypes() { return R"( %si_ty = OpTypeSampledImage %i_ty %p_si_ty = OpTypePointer UniformConstant %si_ty %array_si_ty = OpTypeArray %si_ty %uint_3 %p_array_si_ty = OpTypePointer UniformConstant %array_si_ty %rtarray_si_ty = OpTypeRuntimeArray %si_ty %p_rtarray_si_ty = OpTypePointer UniformConstant %rtarray_si_ty )"; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionBody_ScalarNoChange) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %float %i_ty %s_ty %p_i_ty %p_s_ty %f_ty = OpTypeFunction %float %i_ty %s_ty %p_i_ty %p_s_ty ; CHECK: %f = OpFunction %float None %f_ty ; CHECK-NEXT: OpFunctionParameter %i_ty ; CHECK-NEXT: OpFunctionParameter %s_ty ; CHECK-NEXT: OpFunctionParameter %p_i_ty ; CHECK-NEXT: OpFunctionParameter %p_s_ty ; CHECK-NEXT: OpLabel %f = OpFunction %float None %f_ty %100 = OpFunctionParameter %i_ty %101 = OpFunctionParameter %s_ty %102 = OpFunctionParameter %p_i_ty %103 = OpFunctionParameter %p_s_ty %110 = OpLabel OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionBody_SampledImage_OpImageSample) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %v4float %uint %i_ty %s_ty %float %f_ty = OpTypeFunction %v4float %uint %si_ty %float ; CHECK: %f = OpFunction %v4float None %f_ty ; CHECK: OpFunctionParameter %uint ; CHECK-NEXT: %[[i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: OpFunctionParameter %float ; CHECK-NEXT: OpLabel ; CHECK-NEXT: %[[si:\w+]] = OpSampledImage %si_ty %[[i]] %[[s]] ; CHECK-NEXT: %200 = OpImageSampleExplicitLod %v4float %[[si]] %13 Lod %float_0 ; CHECK-NEXT: OpReturnValue %200 %f = OpFunction %v4float None %f_ty %100 = OpFunctionParameter %uint %101 = OpFunctionParameter %si_ty ; replace this %110 = OpFunctionParameter %float %120 = OpLabel %200 = OpImageSampleExplicitLod %v4float %101 %13 Lod %float_0 OpReturnValue %200 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionBody_SampledImage_OpImage) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %uint %i_ty %s_ty %float %f_ty = OpTypeFunction %void %uint %si_ty %float ; CHECK: %f = OpFunction %void None %f_ty ; CHECK: OpFunctionParameter %uint ; CHECK-NEXT: %[[i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: OpFunctionParameter %float ; CHECK-NEXT: OpLabel ; CHECK-NEXT: %[[si:\w+]] = OpSampledImage %si_ty %[[i]] %[[s]] ; CHECK-NEXT: %200 = OpImage %i_ty %[[si]] ; CHECK-NEXT: OpReturn %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %uint %101 = OpFunctionParameter %si_ty ; replace this %110 = OpFunctionParameter %float %120 = OpLabel %200 = OpImage %i_ty %101 OpReturn OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionBody_PtrSampledImage) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %v4float %uint %p_i_ty %p_s_ty %float %f_ty = OpTypeFunction %v4float %uint %p_si_ty %float ; CHECK: %f = OpFunction %v4float None %f_ty ; CHECK-NEXT: OpFunctionParameter %uint ; CHECK-NEXT: %[[pi:\w+]] = OpFunctionParameter %p_i_ty ; CHECK-NEXT: %[[ps:\w+]] = OpFunctionParameter %p_s_ty ; CHECK-NEXT: OpFunctionParameter %float ; CHECK-NEXT: OpLabel ; CHECK-NEXT: %[[i:\w+]] = OpLoad %i_ty %[[pi]] ; CHECK-NEXT: %[[s:\w+]] = OpLoad %s_ty %[[ps]] ; CHECK-NEXT: %[[si:\w+]] = OpSampledImage %si_ty %[[i]] %[[s]] ; CHECK-NEXT: %200 = OpImageSampleExplicitLod %v4float %[[si]] %13 Lod %float_0 ; CHECK-NEXT: OpReturnValue %200 %f = OpFunction %v4float None %f_ty %100 = OpFunctionParameter %uint %101 = OpFunctionParameter %p_si_ty ; replace this %110 = OpFunctionParameter %float %120 = OpLabel %121 = OpLoad %si_ty %101 %200 = OpImageSampleExplicitLod %v4float %121 %13 Lod %float_0 OpReturnValue %200 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_NoImageOrSampler_NoChange) { const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %uint %float %f_ty = OpTypeFunction %void %uint %float %caller_ty = OpTypeFunction %float ; make it return non-void otherwise it's just like main ; The called function does not change ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: = OpFunctionParameter %uint ; CHECK-NEXT: = OpFunctionParameter %float ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %uint %101 = OpFunctionParameter %float %110 = OpLabel OpReturn OpFunctionEnd ; The caller does not change ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_arg = OpCopyObject %uint %uint_0 ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg %float_0 ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_entry = OpLabel %caller_arg = OpCopyObject %uint %uint_0 %caller_call = OpFunctionCall %void %f %caller_arg %float_0 OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); // We still get a success-with-change result because the boilerplate included // combined types, which were removed. EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_Image_NoChange) { const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + BasicTypes() + ITypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %i_ty %f_ty = OpTypeFunction %void %i_ty %caller_ty = OpTypeFunction %float %i_ty ; The called function does not change ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: = OpFunctionParameter %i_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %i_ty %110 = OpLabel OpReturn OpFunctionEnd ; The caller does not change ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %caller_arg = OpFunctionParameter %i_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %i_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_Sampler_NoChange) { const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + BasicTypes() + ITypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %s_ty %f_ty = OpTypeFunction %void %s_ty %caller_ty = OpTypeFunction %float %s_ty ; The called function does not change ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: = OpFunctionParameter %s_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %s_ty %110 = OpLabel OpReturn OpFunctionEnd ; The caller does not change ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %caller_arg = OpFunctionParameter %s_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %s_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrImage_NoChange) { const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + BasicTypes() + ITypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %p_i_ty %f_ty = OpTypeFunction %void %p_i_ty %caller_ty = OpTypeFunction %float %p_i_ty ; The called function does not change ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: = OpFunctionParameter %p_i_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %p_i_ty %110 = OpLabel OpReturn OpFunctionEnd ; The caller does not change ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %caller_arg = OpFunctionParameter %p_i_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %p_i_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrSampler_NoChange) { const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + BasicTypes() + ITypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %p_s_ty %f_ty = OpTypeFunction %void %p_s_ty %caller_ty = OpTypeFunction %float %p_s_ty ; The called function does not change ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: = OpFunctionParameter %p_s_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %p_s_ty %110 = OpLabel OpReturn OpFunctionEnd ; The caller does not change ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %caller_arg = OpFunctionParameter %p_s_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %p_s_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_SampledImage_Split) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty %f_ty = OpTypeFunction %void %si_ty %caller_ty = OpTypeFunction %float %si_ty ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %si_ty %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %si_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_SampledImageDuplicatedArg_Split) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty %i_ty %s_ty %f_ty = OpTypeFunction %void %si_ty %si_ty %caller_ty = OpTypeFunction %float %si_ty ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_i_0:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s_0:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %[[callee_i_1:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s_1:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %si_ty %101 = OpFunctionParameter %si_ty %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] %[[caller_i]] %[[caller_s]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %si_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_SampledImageTwoDistinct_Split) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty %i_ty %s_ty %f_ty = OpTypeFunction %void %si_ty %si_ty %caller_ty = OpTypeFunction %float %si_ty %si_ty ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_i_0:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s_0:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %[[callee_i_1:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s_1:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %si_ty %101 = OpFunctionParameter %si_ty %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[caller_i_0:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[caller_s_0:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %[[caller_i_1:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[caller_s_1:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i_0]] %[[caller_s_0]] %[[caller_i_1]] %[[caller_s_1]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %si_ty %201 = OpFunctionParameter %si_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg %201 OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_SampledImageAndCopy_Split) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty %i_ty %s_ty %f_ty = OpTypeFunction %void %si_ty %si_ty ; CHECK: %caller_ty = OpTypeFunction %float %i_ty %s_ty %caller_ty = OpTypeFunction %float %si_ty ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_i_0:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s_0:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %[[callee_i_1:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s_1:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %si_ty %101 = OpFunctionParameter %si_ty %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] %[[caller_i]] %[[caller_s]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %si_ty %caller_entry = OpLabel %copy = OpCopyObject %si_ty %caller_arg %caller_call = OpFunctionCall %void %f %caller_arg %copy OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_SampledImageSurrounded_Split) { // Test indexing by surrounding the sampled image parameter with other // arguments that should not be touched. const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %float %i_ty %s_ty %uint %f_ty = OpTypeFunction %void %float %si_ty %uint ; CHECK: %caller_ty = OpTypeFunction %float %uint %i_ty %s_ty %float %caller_ty = OpTypeFunction %float %uint %si_ty %float ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_f:\w+]] = OpFunctionParameter %float ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %[[callee_u:\w+]] = OpFunctionParameter %uint ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %99 = OpFunctionParameter %float %100 = OpFunctionParameter %si_ty %101 = OpFunctionParameter %uint %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[u_param:\w+]] = OpFunctionParameter %uint ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty ; CHECK-NEXT: %[[f_param:\w+]] = OpFunctionParameter %float ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[f_param]] %[[caller_i]] %[[caller_s]] %[[u_param]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %200 = OpFunctionParameter %uint %caller_arg = OpFunctionParameter %si_ty %201 = OpFunctionParameter %float %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %201 %caller_arg %200 OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrSampledImage_Split) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %f_ty = OpTypeFunction %void %p_i_ty %p_s_ty %f_ty = OpTypeFunction %void %p_si_ty %caller_ty = OpTypeFunction %float %p_si_ty ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %p_i_ty ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %p_s_ty ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %p_si_ty %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %p_i_ty ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %p_s_ty ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %p_si_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrArraySampledImage_Split) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %[[array_i_ty:\w+]] = OpTypeArray %i_ty %uint_3 ; CHECK: %[[p_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] ; CHECK: %[[array_s_ty:\w+]] = OpTypeArray %s_ty %uint_3 ; CHECK: %[[p_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] ; CHECK: %f_ty = OpTypeFunction %void %[[p_array_i_ty]] %[[p_array_s_ty]] %f_ty = OpTypeFunction %void %p_array_si_ty %caller_ty = OpTypeFunction %float %p_array_si_ty ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %p_array_si_ty %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %p_array_si_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrRtArraySampledImage_Split) { const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + NamedCaller() + BasicTypes() + ITypes() + CombinedTypes() + R"( ; CHECK: %[[array_i_ty:\w+]] = OpTypeRuntimeArray %i_ty ; CHECK: %[[p_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] ; CHECK: %[[array_s_ty:\w+]] = OpTypeRuntimeArray ; CHECK: %[[p_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] ; CHECK: %f_ty = OpTypeFunction %void %[[p_array_i_ty]] %[[p_array_s_ty]] %f_ty = OpTypeFunction %void %p_rtarray_si_ty %caller_ty = OpTypeFunction %float %p_rtarray_si_ty ; Call function arg is split. We've checked these details in other tests. ; CHECK: %f = OpFunction %void None %f_ty ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] ; CHECK-NEXT: = OpLabel ; CHECK-NEXT: OpReturn ; CHECK-NEXT: OpFunctionEnd %f = OpFunction %void None %f_ty %100 = OpFunctionParameter %p_rtarray_si_ty %110 = OpLabel OpReturn OpFunctionEnd ; CHECK: %caller = OpFunction %float None %caller_ty ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] ; CHECK-NEXT: %caller_entry = OpLabel ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] ; CHECK-NEXT: OpReturnValue %float_0 ; CHECK-NEXT: OpFunctionEnd %caller = OpFunction %float None %caller_ty %caller_arg = OpFunctionParameter %p_rtarray_si_ty %caller_entry = OpLabel %caller_call = OpFunctionCall %void %f %caller_arg OpReturnValue %float_0 OpFunctionEnd )" + Main(); auto [disasm, status] = SinglePassRunAndMatch( kTest, /* do_validation= */ true); EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; } } // namespace } // namespace opt } // namespace spvtools