Fixup binding generator for duplicate bindings.

When using multiple entry points it's possible for bindings to be
duplicated and existing in different binding type hashes. Currently this
would trigger an error in Tint as it would report a duplicate binding.

This CL changes the duplicate checking to allow duplicate entries as
long as the `src,dst` pair are the same. So, different entry points can
re-bindd the same source to the same dest without issue. This fixes up
the `tint` usage as it always remaps a binding to itself, so they are
always the same.

This does not effect Dawn as Dawn always runs the single entry point
transformation.

Bug: tint:2076
Change-Id: I3a08ac77a5a640f08bca37a75bbd47c423fff8d1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/158840
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/spirv/writer/common/option_builder.cc b/src/tint/lang/spirv/writer/common/option_builder.cc
index 6b66447..55181fc 100644
--- a/src/tint/lang/spirv/writer/common/option_builder.cc
+++ b/src/tint/lang/spirv/writer/common/option_builder.cc
@@ -32,31 +32,41 @@
 namespace tint::spirv::writer {
 
 bool ValidateBindingOptions(const Options& options, diag::List& diagnostics) {
-    tint::Hashset<tint::BindingPoint, 8> seen_wgsl_bindings{};
-    tint::Hashset<binding::BindingInfo, 8> seen_spirv_bindings{};
+    tint::Hashmap<tint::BindingPoint, binding::BindingInfo, 8> seen_wgsl_bindings{};
+    tint::Hashmap<binding::BindingInfo, tint::BindingPoint, 8> seen_spirv_bindings{};
 
-    auto wgsl_seen = [&diagnostics, &seen_wgsl_bindings](const tint::BindingPoint& info) -> bool {
-        if (seen_wgsl_bindings.Contains(info)) {
-            std::stringstream str;
-            str << "found duplicate WGSL binding point: " << info;
+    // Both wgsl_seen and spirv_seen check to see if the pair of [src, dst] are unique. If we have
+    // multiple entries that map the same [src, dst] pair, that's fine. We treat it as valid as it's
+    // possible for multiple entry points to use the remapper at the same time. If the pair doesn't
+    // match, then we report an error about a duplicate binding point.
 
-            diagnostics.add_error(diag::System::Writer, str.str());
-            return true;
+    auto wgsl_seen = [&diagnostics, &seen_wgsl_bindings](const tint::BindingPoint& src,
+                                                         const binding::BindingInfo& dst) -> bool {
+        if (auto binding = seen_wgsl_bindings.Find(src)) {
+            if (*binding != dst) {
+                std::stringstream str;
+                str << "found duplicate WGSL binding point: " << src;
+
+                diagnostics.add_error(diag::System::Writer, str.str());
+                return true;
+            }
         }
-        seen_wgsl_bindings.Add(info);
+        seen_wgsl_bindings.Add(src, dst);
         return false;
     };
 
-    auto spirv_seen = [&diagnostics,
-                       &seen_spirv_bindings](const binding::BindingInfo& info) -> bool {
-        if (seen_spirv_bindings.Contains(info)) {
-            std::stringstream str;
-            str << "found duplicate SPIR-V binding point: [group: " << info.group
-                << ", binding: " << info.binding << "]";
-            diagnostics.add_error(diag::System::Writer, str.str());
-            return true;
+    auto spirv_seen = [&diagnostics, &seen_spirv_bindings](const binding::BindingInfo& src,
+                                                           const tint::BindingPoint& dst) -> bool {
+        if (auto binding = seen_spirv_bindings.Find(src)) {
+            if (*binding != dst) {
+                std::stringstream str;
+                str << "found duplicate SPIR-V binding point: [group: " << src.group
+                    << ", binding: " << src.binding << "]";
+                diagnostics.add_error(diag::System::Writer, str.str());
+                return true;
+            }
         }
-        seen_spirv_bindings.Add(info);
+        seen_spirv_bindings.Add(src, dst);
         return false;
     };
 
@@ -65,11 +75,11 @@
             const auto& src_binding = it.first;
             const auto& dst_binding = it.second;
 
-            if (wgsl_seen(src_binding)) {
+            if (wgsl_seen(src_binding, dst_binding)) {
                 return false;
             }
 
-            if (spirv_seen(dst_binding)) {
+            if (spirv_seen(dst_binding, src_binding)) {
                 return false;
             }
         }
@@ -104,20 +114,20 @@
         const auto& metadata = it.second.metadata;
 
         // Validate with the actual source regardless of what the remapper will do
-        if (wgsl_seen(src_binding)) {
+        if (wgsl_seen(src_binding, plane0)) {
             diagnostics.add_note(diag::System::Writer, "when processing external_texture", {});
             return false;
         }
 
-        if (spirv_seen(plane0)) {
+        if (spirv_seen(plane0, src_binding)) {
             diagnostics.add_note(diag::System::Writer, "when processing external_texture", {});
             return false;
         }
-        if (spirv_seen(plane1)) {
+        if (spirv_seen(plane1, src_binding)) {
             diagnostics.add_note(diag::System::Writer, "when processing external_texture", {});
             return false;
         }
-        if (spirv_seen(metadata)) {
+        if (spirv_seen(metadata, src_binding)) {
             diagnostics.add_note(diag::System::Writer, "when processing external_texture", {});
             return false;
         }
diff --git a/src/tint/lang/spirv/writer/common/options.h b/src/tint/lang/spirv/writer/common/options.h
index c0f5e28..96b585e 100644
--- a/src/tint/lang/spirv/writer/common/options.h
+++ b/src/tint/lang/spirv/writer/common/options.h
@@ -49,6 +49,10 @@
     inline bool operator==(const BindingInfo& rhs) const {
         return group == rhs.group && binding == rhs.binding;
     }
+    /// Inequality operator
+    /// @param rhs the BindingInfo to compare against
+    /// @returns true if this BindingInfo is not equal to `rhs`
+    inline bool operator!=(const BindingInfo& rhs) const { return !(*this == rhs); }
 
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(group, binding);
diff --git a/src/tint/lang/spirv/writer/helpers/generate_bindings.cc b/src/tint/lang/spirv/writer/helpers/generate_bindings.cc
index 1f429ba..21ef88b 100644
--- a/src/tint/lang/spirv/writer/helpers/generate_bindings.cc
+++ b/src/tint/lang/spirv/writer/helpers/generate_bindings.cc
@@ -50,28 +50,12 @@
 
     Bindings bindings{};
 
-    std::unordered_set<tint::BindingPoint> seen_binding_points;
-
     // Collect next valid binding number per group
     Hashmap<uint32_t, uint32_t, 4> group_to_next_binding_number;
     Vector<tint::BindingPoint, 4> ext_tex_bps;
     for (auto* var : program.AST().GlobalVariables()) {
         if (auto* sem_var = program.Sem().Get(var)->As<sem::GlobalVariable>()) {
             if (auto bp = sem_var->BindingPoint()) {
-                // This is a bit of a hack. The binding points must be unique over all the `binding`
-                // entries. But, this is looking at _all_ entry points where bindings can overlap.
-                // In the case where both entry points used the same type (uniform, sampler, etc)
-                // then it would be fine as it just overwrites with itself. But, if one entry point
-                // has a uniform and the other a sampler at the same (group,binding) pair then we'll
-                // get a validation error due to duplicate WGSL bindings.
-                //
-                // For generating bindings we don't really care as we always map to itself, so if it
-                // exists anywhere, we just pretend that's the only one.
-                if (seen_binding_points.find(*bp) != seen_binding_points.end()) {
-                    continue;
-                }
-                seen_binding_points.emplace(*bp);
-
                 if (auto val = group_to_next_binding_number.Find(bp->group)) {
                     *val = std::max(*val, bp->binding + 1);
                 } else {
diff --git a/test/tint/bug/tint/2076.wgsl b/test/tint/bug/tint/2076.wgsl
new file mode 100644
index 0000000..2e96604
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl
@@ -0,0 +1,9 @@
+@group (0) @binding(1)
+var Sampler: sampler;
+
+@group(0) @binding(1)
+var randomTexture: texture_external;
+
+@group (0) @binding(2)
+var depthTexture: texture_2d<f32>;
+
diff --git a/test/tint/bug/tint/2076.wgsl.expected.dxc.hlsl b/test/tint/bug/tint/2076.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..0030a48
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl.expected.dxc.hlsl
@@ -0,0 +1,12 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+Texture2D<float4> ext_tex_plane_1 : register(t3);
+cbuffer cbuffer_ext_tex_params : register(b4) {
+  uint4 ext_tex_params[13];
+};
+SamplerState tint_symbol : register(s1);
+Texture2D<float4> randomTexture : register(t1);
+Texture2D<float4> depthTexture : register(t2);
diff --git a/test/tint/bug/tint/2076.wgsl.expected.fxc.hlsl b/test/tint/bug/tint/2076.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..0030a48
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl.expected.fxc.hlsl
@@ -0,0 +1,12 @@
+[numthreads(1, 1, 1)]
+void unused_entry_point() {
+  return;
+}
+
+Texture2D<float4> ext_tex_plane_1 : register(t3);
+cbuffer cbuffer_ext_tex_params : register(b4) {
+  uint4 ext_tex_params[13];
+};
+SamplerState tint_symbol : register(s1);
+Texture2D<float4> randomTexture : register(t1);
+Texture2D<float4> depthTexture : register(t2);
diff --git a/test/tint/bug/tint/2076.wgsl.expected.glsl b/test/tint/bug/tint/2076.wgsl.expected.glsl
new file mode 100644
index 0000000..0027ffd
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl.expected.glsl
@@ -0,0 +1,51 @@
+#version 310 es
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void unused_entry_point() {
+  return;
+}
+struct GammaTransferParams {
+  float G;
+  float A;
+  float B;
+  float C;
+  float D;
+  float E;
+  float F;
+  uint padding;
+};
+
+struct ExternalTextureParams {
+  uint numPlanes;
+  uint doYuvToRgbConversionOnly;
+  uint pad;
+  uint pad_1;
+  mat3x4 yuvToRgbConversionMatrix;
+  GammaTransferParams gammaDecodeParams;
+  GammaTransferParams gammaEncodeParams;
+  mat3 gamutConversionMatrix;
+  mat3x2 coordTransformationMatrix;
+  uint pad_2;
+  uint pad_3;
+};
+
+struct ExternalTextureParams_std140 {
+  uint numPlanes;
+  uint doYuvToRgbConversionOnly;
+  uint pad;
+  uint pad_1;
+  mat3x4 yuvToRgbConversionMatrix;
+  GammaTransferParams gammaDecodeParams;
+  GammaTransferParams gammaEncodeParams;
+  mat3 gamutConversionMatrix;
+  vec2 coordTransformationMatrix_0;
+  vec2 coordTransformationMatrix_1;
+  vec2 coordTransformationMatrix_2;
+  uint pad_2;
+  uint pad_3;
+};
+
+layout(binding = 4, std140) uniform ext_tex_params_block_std140_ubo {
+  ExternalTextureParams_std140 inner;
+} ext_tex_params;
+
diff --git a/test/tint/bug/tint/2076.wgsl.expected.ir.spvasm b/test/tint/bug/tint/2076.wgsl.expected.ir.spvasm
new file mode 100644
index 0000000..ff281a8
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl.expected.ir.spvasm
@@ -0,0 +1,96 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 1
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %Sampler "Sampler"
+               OpName %randomTexture_plane0 "randomTexture_plane0"
+               OpName %randomTexture_plane1 "randomTexture_plane1"
+               OpMemberName %tint_ExternalTextureParams_std140 0 "numPlanes"
+               OpMemberName %tint_ExternalTextureParams_std140 1 "doYuvToRgbConversionOnly"
+               OpMemberName %tint_ExternalTextureParams_std140 2 "yuvToRgbConversionMatrix"
+               OpMemberName %tint_GammaTransferParams 0 "G"
+               OpMemberName %tint_GammaTransferParams 1 "A"
+               OpMemberName %tint_GammaTransferParams 2 "B"
+               OpMemberName %tint_GammaTransferParams 3 "C"
+               OpMemberName %tint_GammaTransferParams 4 "D"
+               OpMemberName %tint_GammaTransferParams 5 "E"
+               OpMemberName %tint_GammaTransferParams 6 "F"
+               OpMemberName %tint_GammaTransferParams 7 "padding"
+               OpName %tint_GammaTransferParams "tint_GammaTransferParams"
+               OpMemberName %tint_ExternalTextureParams_std140 3 "gammaDecodeParams"
+               OpMemberName %tint_ExternalTextureParams_std140 4 "gammaEncodeParams"
+               OpMemberName %tint_ExternalTextureParams_std140 5 "gamutConversionMatrix_col0"
+               OpMemberName %tint_ExternalTextureParams_std140 6 "gamutConversionMatrix_col1"
+               OpMemberName %tint_ExternalTextureParams_std140 7 "gamutConversionMatrix_col2"
+               OpMemberName %tint_ExternalTextureParams_std140 8 "coordTransformationMatrix_col0"
+               OpMemberName %tint_ExternalTextureParams_std140 9 "coordTransformationMatrix_col1"
+               OpMemberName %tint_ExternalTextureParams_std140 10 "coordTransformationMatrix_col2"
+               OpName %tint_ExternalTextureParams_std140 "tint_ExternalTextureParams_std140"
+               OpMemberName %tint_symbol_1_std140 0 "tint_symbol"
+               OpName %tint_symbol_1_std140 "tint_symbol_1_std140"
+               OpName %depthTexture "depthTexture"
+               OpName %unused_entry_point "unused_entry_point"
+               OpDecorate %Sampler DescriptorSet 0
+               OpDecorate %Sampler Binding 1
+               OpDecorate %randomTexture_plane0 DescriptorSet 0
+               OpDecorate %randomTexture_plane0 Binding 1
+               OpDecorate %randomTexture_plane1 DescriptorSet 0
+               OpDecorate %randomTexture_plane1 Binding 3
+               OpMemberDecorate %tint_ExternalTextureParams_std140 0 Offset 0
+               OpMemberDecorate %tint_ExternalTextureParams_std140 1 Offset 4
+               OpMemberDecorate %tint_ExternalTextureParams_std140 2 Offset 16
+               OpMemberDecorate %tint_ExternalTextureParams_std140 2 ColMajor
+               OpMemberDecorate %tint_ExternalTextureParams_std140 2 MatrixStride 16
+               OpMemberDecorate %tint_GammaTransferParams 0 Offset 0
+               OpMemberDecorate %tint_GammaTransferParams 1 Offset 4
+               OpMemberDecorate %tint_GammaTransferParams 2 Offset 8
+               OpMemberDecorate %tint_GammaTransferParams 3 Offset 12
+               OpMemberDecorate %tint_GammaTransferParams 4 Offset 16
+               OpMemberDecorate %tint_GammaTransferParams 5 Offset 20
+               OpMemberDecorate %tint_GammaTransferParams 6 Offset 24
+               OpMemberDecorate %tint_GammaTransferParams 7 Offset 28
+               OpMemberDecorate %tint_ExternalTextureParams_std140 3 Offset 64
+               OpMemberDecorate %tint_ExternalTextureParams_std140 4 Offset 96
+               OpMemberDecorate %tint_ExternalTextureParams_std140 5 Offset 128
+               OpMemberDecorate %tint_ExternalTextureParams_std140 6 Offset 144
+               OpMemberDecorate %tint_ExternalTextureParams_std140 7 Offset 160
+               OpMemberDecorate %tint_ExternalTextureParams_std140 8 Offset 176
+               OpMemberDecorate %tint_ExternalTextureParams_std140 9 Offset 184
+               OpMemberDecorate %tint_ExternalTextureParams_std140 10 Offset 192
+               OpMemberDecorate %tint_symbol_1_std140 0 Offset 0
+               OpDecorate %tint_symbol_1_std140 Block
+               OpDecorate %9 DescriptorSet 0
+               OpDecorate %9 Binding 4
+               OpDecorate %depthTexture DescriptorSet 0
+               OpDecorate %depthTexture Binding 2
+          %3 = OpTypeSampler
+%_ptr_UniformConstant_3 = OpTypePointer UniformConstant %3
+    %Sampler = OpVariable %_ptr_UniformConstant_3 UniformConstant
+      %float = OpTypeFloat 32
+          %6 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_6 = OpTypePointer UniformConstant %6
+%randomTexture_plane0 = OpVariable %_ptr_UniformConstant_6 UniformConstant
+%randomTexture_plane1 = OpVariable %_ptr_UniformConstant_6 UniformConstant
+       %uint = OpTypeInt 32 0
+    %v4float = OpTypeVector %float 4
+%mat3v4float = OpTypeMatrix %v4float 3
+%tint_GammaTransferParams = OpTypeStruct %float %float %float %float %float %float %float %uint
+    %v3float = OpTypeVector %float 3
+    %v2float = OpTypeVector %float 2
+%tint_ExternalTextureParams_std140 = OpTypeStruct %uint %uint %mat3v4float %tint_GammaTransferParams %tint_GammaTransferParams %v3float %v3float %v3float %v2float %v2float %v2float
+%tint_symbol_1_std140 = OpTypeStruct %tint_ExternalTextureParams_std140
+%_ptr_Uniform_tint_symbol_1_std140 = OpTypePointer Uniform %tint_symbol_1_std140
+          %9 = OpVariable %_ptr_Uniform_tint_symbol_1_std140 Uniform
+%_ptr_UniformConstant_6_0 = OpTypePointer UniformConstant %6
+%depthTexture = OpVariable %_ptr_UniformConstant_6_0 UniformConstant
+       %void = OpTypeVoid
+         %23 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %23
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/bug/tint/2076.wgsl.expected.msl b/test/tint/bug/tint/2076.wgsl.expected.msl
new file mode 100644
index 0000000..c3c3d8c
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl.expected.msl
@@ -0,0 +1,51 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct tint_packed_vec3_f32_array_element {
+  packed_float3 elements;
+};
+
+struct GammaTransferParams {
+  float G;
+  float A;
+  float B;
+  float C;
+  float D;
+  float E;
+  float F;
+  uint padding;
+};
+
+struct ExternalTextureParams_tint_packed_vec3 {
+  uint numPlanes;
+  uint doYuvToRgbConversionOnly;
+  float3x4 yuvToRgbConversionMatrix;
+  GammaTransferParams gammaDecodeParams;
+  GammaTransferParams gammaEncodeParams;
+  tint_array<tint_packed_vec3_f32_array_element, 3> gamutConversionMatrix;
+  float3x2 coordTransformationMatrix;
+};
+
+struct ExternalTextureParams {
+  uint numPlanes;
+  uint doYuvToRgbConversionOnly;
+  float3x4 yuvToRgbConversionMatrix;
+  GammaTransferParams gammaDecodeParams;
+  GammaTransferParams gammaEncodeParams;
+  float3x3 gamutConversionMatrix;
+  float3x2 coordTransformationMatrix;
+};
+
diff --git a/test/tint/bug/tint/2076.wgsl.expected.spvasm b/test/tint/bug/tint/2076.wgsl.expected.spvasm
new file mode 100644
index 0000000..8502b70
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl.expected.spvasm
@@ -0,0 +1,96 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
+               OpExecutionMode %unused_entry_point LocalSize 1 1 1
+               OpName %ext_tex_plane_1 "ext_tex_plane_1"
+               OpName %ext_tex_params_block_std140 "ext_tex_params_block_std140"
+               OpMemberName %ext_tex_params_block_std140 0 "inner"
+               OpName %ExternalTextureParams_std140 "ExternalTextureParams_std140"
+               OpMemberName %ExternalTextureParams_std140 0 "numPlanes"
+               OpMemberName %ExternalTextureParams_std140 1 "doYuvToRgbConversionOnly"
+               OpMemberName %ExternalTextureParams_std140 2 "yuvToRgbConversionMatrix"
+               OpMemberName %ExternalTextureParams_std140 3 "gammaDecodeParams"
+               OpName %GammaTransferParams "GammaTransferParams"
+               OpMemberName %GammaTransferParams 0 "G"
+               OpMemberName %GammaTransferParams 1 "A"
+               OpMemberName %GammaTransferParams 2 "B"
+               OpMemberName %GammaTransferParams 3 "C"
+               OpMemberName %GammaTransferParams 4 "D"
+               OpMemberName %GammaTransferParams 5 "E"
+               OpMemberName %GammaTransferParams 6 "F"
+               OpMemberName %GammaTransferParams 7 "padding"
+               OpMemberName %ExternalTextureParams_std140 4 "gammaEncodeParams"
+               OpMemberName %ExternalTextureParams_std140 5 "gamutConversionMatrix"
+               OpMemberName %ExternalTextureParams_std140 6 "coordTransformationMatrix_0"
+               OpMemberName %ExternalTextureParams_std140 7 "coordTransformationMatrix_1"
+               OpMemberName %ExternalTextureParams_std140 8 "coordTransformationMatrix_2"
+               OpName %ext_tex_params "ext_tex_params"
+               OpName %Sampler "Sampler"
+               OpName %randomTexture "randomTexture"
+               OpName %depthTexture "depthTexture"
+               OpName %unused_entry_point "unused_entry_point"
+               OpDecorate %ext_tex_plane_1 DescriptorSet 0
+               OpDecorate %ext_tex_plane_1 Binding 3
+               OpDecorate %ext_tex_params_block_std140 Block
+               OpMemberDecorate %ext_tex_params_block_std140 0 Offset 0
+               OpMemberDecorate %ExternalTextureParams_std140 0 Offset 0
+               OpMemberDecorate %ExternalTextureParams_std140 1 Offset 4
+               OpMemberDecorate %ExternalTextureParams_std140 2 Offset 16
+               OpMemberDecorate %ExternalTextureParams_std140 2 ColMajor
+               OpMemberDecorate %ExternalTextureParams_std140 2 MatrixStride 16
+               OpMemberDecorate %ExternalTextureParams_std140 3 Offset 64
+               OpMemberDecorate %GammaTransferParams 0 Offset 0
+               OpMemberDecorate %GammaTransferParams 1 Offset 4
+               OpMemberDecorate %GammaTransferParams 2 Offset 8
+               OpMemberDecorate %GammaTransferParams 3 Offset 12
+               OpMemberDecorate %GammaTransferParams 4 Offset 16
+               OpMemberDecorate %GammaTransferParams 5 Offset 20
+               OpMemberDecorate %GammaTransferParams 6 Offset 24
+               OpMemberDecorate %GammaTransferParams 7 Offset 28
+               OpMemberDecorate %ExternalTextureParams_std140 4 Offset 96
+               OpMemberDecorate %ExternalTextureParams_std140 5 Offset 128
+               OpMemberDecorate %ExternalTextureParams_std140 5 ColMajor
+               OpMemberDecorate %ExternalTextureParams_std140 5 MatrixStride 16
+               OpMemberDecorate %ExternalTextureParams_std140 6 Offset 176
+               OpMemberDecorate %ExternalTextureParams_std140 7 Offset 184
+               OpMemberDecorate %ExternalTextureParams_std140 8 Offset 192
+               OpDecorate %ext_tex_params NonWritable
+               OpDecorate %ext_tex_params DescriptorSet 0
+               OpDecorate %ext_tex_params Binding 4
+               OpDecorate %Sampler DescriptorSet 0
+               OpDecorate %Sampler Binding 1
+               OpDecorate %randomTexture DescriptorSet 0
+               OpDecorate %randomTexture Binding 1
+               OpDecorate %depthTexture DescriptorSet 0
+               OpDecorate %depthTexture Binding 2
+      %float = OpTypeFloat 32
+          %3 = OpTypeImage %float 2D 0 0 0 1 Unknown
+%_ptr_UniformConstant_3 = OpTypePointer UniformConstant %3
+%ext_tex_plane_1 = OpVariable %_ptr_UniformConstant_3 UniformConstant
+       %uint = OpTypeInt 32 0
+    %v4float = OpTypeVector %float 4
+%mat3v4float = OpTypeMatrix %v4float 3
+%GammaTransferParams = OpTypeStruct %float %float %float %float %float %float %float %uint
+    %v3float = OpTypeVector %float 3
+%mat3v3float = OpTypeMatrix %v3float 3
+    %v2float = OpTypeVector %float 2
+%ExternalTextureParams_std140 = OpTypeStruct %uint %uint %mat3v4float %GammaTransferParams %GammaTransferParams %mat3v3float %v2float %v2float %v2float
+%ext_tex_params_block_std140 = OpTypeStruct %ExternalTextureParams_std140
+%_ptr_Uniform_ext_tex_params_block_std140 = OpTypePointer Uniform %ext_tex_params_block_std140
+%ext_tex_params = OpVariable %_ptr_Uniform_ext_tex_params_block_std140 Uniform
+         %18 = OpTypeSampler
+%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
+    %Sampler = OpVariable %_ptr_UniformConstant_18 UniformConstant
+%randomTexture = OpVariable %_ptr_UniformConstant_3 UniformConstant
+%depthTexture = OpVariable %_ptr_UniformConstant_3 UniformConstant
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+%unused_entry_point = OpFunction %void None %21
+         %24 = OpLabel
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/bug/tint/2076.wgsl.expected.wgsl b/test/tint/bug/tint/2076.wgsl.expected.wgsl
new file mode 100644
index 0000000..376de51
--- /dev/null
+++ b/test/tint/bug/tint/2076.wgsl.expected.wgsl
@@ -0,0 +1,5 @@
+@group(0) @binding(1) var Sampler : sampler;
+
+@group(0) @binding(1) var randomTexture : texture_external;
+
+@group(0) @binding(2) var depthTexture : texture_2d<f32>;