[spirv-reader] Run ResolveBindingConflictsPass pass

Binding conflicts can be produced by SplitCombinedImageSamplerPass, or
be present in the original SPIR-V. If no sampler_mappings are
provided, run this SPIR-V opt pass to resolve conflicts.

Fixed: 435251397
Change-Id: Ieb437aa3ae009d0154283955a89026fbc4eee84a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/256695
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/reader/common/options.h b/src/tint/lang/spirv/reader/common/options.h
index c6a56a9..32d13d3 100644
--- a/src/tint/lang/spirv/reader/common/options.h
+++ b/src/tint/lang/spirv/reader/common/options.h
@@ -46,6 +46,8 @@
     /// Mapping from a SPIR-V Sampler binding point to a WGSL sampler binding
     /// point. This allows remapping samplers which are split out of the
     /// combined texture/sampler pairs in SPIR-V.
+    /// If this map is empty, any binding conflicts will be automatically resolved by incrementing
+    /// binding numbers until they are unique.
     std::unordered_map<BindingPoint, BindingPoint> sampler_mappings{};
 };
 
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 5e82caf..2704f96 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -44,6 +44,7 @@
 TINT_BEGIN_DISABLE_WARNING(WEAK_VTABLES);
 TINT_BEGIN_DISABLE_WARNING(UNSAFE_BUFFER_USAGE);
 #include "source/opt/build_module.h"
+#include "source/opt/resolve_binding_conflicts_pass.h"
 #include "source/opt/split_combined_image_sampler_pass.h"
 TINT_END_DISABLE_WARNING(UNSAFE_BUFFER_USAGE);
 TINT_END_DISABLE_WARNING(WEAK_VTABLES);
@@ -108,6 +109,7 @@
             return Failure("failed to build the internal representation of the module");
         }
 
+        // Run SPIR-V opt transforms to make the input friendlier for the SPIR-V frontend.
         {
             spvtools::opt::SplitCombinedImageSamplerPass pass;
             auto status = pass.Run(spirv_context_.get());
@@ -115,6 +117,13 @@
                 return Failure("failed to run SplitCombinedImageSamplerPass in SPIR-V opt");
             }
         }
+        if (options_.sampler_mappings.empty()) {
+            spvtools::opt::ResolveBindingConflictsPass pass;
+            auto status = pass.Run(spirv_context_.get());
+            if (status == spvtools::opt::Pass::Status::Failure) {
+                return Failure("failed to run ResolveBindingConflictsPass in SPIR-V opt");
+            }
+        }
 
         // Check for unsupported extensions.
         for (const auto& ext : spirv_context_->extensions()) {
diff --git a/src/tint/lang/spirv/reader/reader_test.cc b/src/tint/lang/spirv/reader/reader_test.cc
index 7b65f1c..9563d39 100644
--- a/src/tint/lang/spirv/reader/reader_test.cc
+++ b/src/tint/lang/spirv/reader/reader_test.cc
@@ -683,5 +683,146 @@
 )");
 }
 
+// In Vulkan it is valid to have a texture and sampler with the same set and binding, so we use the
+// `ResolveBindingConflictsPass` SPIR-V tools pass to remove the conflicts.
+// See crbug.com/435251397.
+TEST_F(SpirvReaderTest, ResolveBindingConflicts) {
+    auto got = Run(R"(
+; SPIR-V
+; Version: 1.0
+; Generator: Google spiregg; 0
+; Bound: 40
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %PS_Viewport "PS_Viewport" %in_var_TEXCOORD0 %out_var_SV_Target
+               OpExecutionMode %PS_Viewport OriginUpperLeft
+               OpSource HLSL 600
+               OpName %type_constants_ "type.constants_"
+               OpMemberName %type_constants_ 0 "constants"
+               OpName %Constants "Constants"
+               OpMemberName %Constants 0 "color"
+               OpMemberName %Constants 1 "scale"
+               OpMemberName %Constants 2 "padding"
+               OpName %constants_ "constants_"
+               OpName %type_2d_image "type.2d.image"
+               OpName %tex "tex"
+               OpName %type_sampler "type.sampler"
+               OpName %samplerTex "samplerTex"
+               OpName %in_var_TEXCOORD0 "in.var.TEXCOORD0"
+               OpName %out_var_SV_Target "out.var.SV_Target"
+               OpName %PS_Viewport "PS_Viewport"
+               OpName %type_sampled_image "type.sampled.image"
+               OpDecorate %in_var_TEXCOORD0 Location 0
+               OpDecorate %out_var_SV_Target Location 0
+               OpDecorate %constants_ DescriptorSet 0
+               OpDecorate %constants_ Binding 0
+               OpDecorate %tex DescriptorSet 0
+               OpDecorate %tex Binding 0
+               OpDecorate %samplerTex DescriptorSet 0
+               OpDecorate %samplerTex Binding 0
+               OpMemberDecorate %Constants 0 Offset 0
+               OpMemberDecorate %Constants 1 Offset 16
+               OpMemberDecorate %Constants 2 Offset 24
+               OpMemberDecorate %type_constants_ 0 Offset 0
+               OpDecorate %type_constants_ Block
+        %int = OpTypeInt 32 1
+      %int_0 = OpConstant %int 0
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %v4float = OpTypeVector %float 4
+    %v2float = OpTypeVector %float 2
+  %Constants = OpTypeStruct %v4float %v2float %v2float
+%type_constants_ = OpTypeStruct %Constants
+%_ptr_Uniform_type_constants_ = OpTypePointer Uniform %type_constants_
+%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+%type_sampler = OpTypeSampler
+%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler
+%_ptr_Input_v2float = OpTypePointer Input %v2float
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+       %void = OpTypeVoid
+         %24 = OpTypeFunction %void
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+%type_sampled_image = OpTypeSampledImage %type_2d_image
+       %bool = OpTypeBool
+ %constants_ = OpVariable %_ptr_Uniform_type_constants_ Uniform
+        %tex = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+ %samplerTex = OpVariable %_ptr_UniformConstant_type_sampler UniformConstant
+%in_var_TEXCOORD0 = OpVariable %_ptr_Input_v2float Input
+%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
+%PS_Viewport = OpFunction %void None %24
+         %27 = OpLabel
+         %28 = OpLoad %v2float %in_var_TEXCOORD0
+         %29 = OpAccessChain %_ptr_Uniform_v4float %constants_ %int_0 %int_0
+         %30 = OpLoad %v4float %29
+         %31 = OpLoad %type_2d_image %tex
+         %32 = OpLoad %type_sampler %samplerTex
+         %33 = OpSampledImage %type_sampled_image %31 %32
+         %34 = OpImageSampleImplicitLod %v4float %33 %28 None
+         %35 = OpFMul %v4float %30 %34
+         %36 = OpCompositeExtract %float %35 3
+         %37 = OpFOrdLessThan %bool %36 %float_0
+               OpSelectionMerge %38 None
+               OpBranchConditional %37 %39 %38
+         %39 = OpLabel
+               OpKill
+         %38 = OpLabel
+               OpStore %out_var_SV_Target %35
+               OpReturn
+               OpFunctionEnd
+)");
+    ASSERT_EQ(got, Success);
+    EXPECT_EQ(got, R"(
+Constants = struct @align(16) {
+  color:vec4<f32> @offset(0)
+  scale:vec2<f32> @offset(16)
+  padding:vec2<f32> @offset(24)
+}
+
+type.constants_ = struct @align(16) {
+  constants:Constants @offset(0)
+}
+
+$B1: {  # root
+  %constants_:ptr<uniform, type.constants_, read> = var undef @binding_point(0, 0)
+  %tex:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(0, 1)
+  %samplerTex:ptr<handle, sampler, read> = var undef @binding_point(0, 2)
+  %out.var.SV_Target:ptr<private, vec4<f32>, read_write> = var undef
+}
+
+%PS_Viewport_inner = func(%in.var.TEXCOORD0:vec2<f32>):void {
+  $B2: {
+    %7:ptr<uniform, vec4<f32>, read> = access %constants_, 0i, 0i
+    %8:vec4<f32> = load %7
+    %9:texture_2d<f32> = load %tex
+    %10:sampler = load %samplerTex
+    %11:vec4<f32> = textureSample %9, %10, %in.var.TEXCOORD0
+    %12:vec4<f32> = mul %8, %11
+    %13:f32 = access %12, 3u
+    %14:bool = lt %13, 0.0f
+    if %14 [t: $B3, f: $B4] {  # if_1
+      $B3: {  # true
+        discard
+        ret
+      }
+      $B4: {  # false
+        exit_if  # if_1
+      }
+    }
+    store %out.var.SV_Target, %12
+    ret
+  }
+}
+%PS_Viewport = @fragment func(%in.var.TEXCOORD0_1:vec2<f32> [@location(0)]):vec4<f32> [@location(0)] {  # %in.var.TEXCOORD0_1: 'in.var.TEXCOORD0'
+  $B5: {
+    %17:void = call %PS_Viewport_inner, %in.var.TEXCOORD0_1
+    %18:vec4<f32> = load %out.var.SV_Target
+    ret %18
+  }
+}
+)");
+}
+
 }  // namespace
 }  // namespace tint::spirv::reader