[tint][ir][val] Reject duplicate binding points in entry points

Also adds a capability for allowing duplicate binding points after
BindingRemapper has run.

Fixes: 416291256

Change-Id: Icff6c1fb4581b1cf5b08af61de6192bb02cdf870
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/242597
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/tint/lang/core/ir/transform/add_empty_entry_point.h b/src/tint/lang/core/ir/transform/add_empty_entry_point.h
index 12a9233..f21041a 100644
--- a/src/tint/lang/core/ir/transform/add_empty_entry_point.h
+++ b/src/tint/lang/core/ir/transform/add_empty_entry_point.h
@@ -41,7 +41,7 @@
 /// The capabilities that the transform can support.
 const core::ir::Capabilities kAddEmptyEntryPointCapabilities{
     core::ir::Capability::kAllowHandleVarsWithoutBindings,
-};
+    core::ir::Capability::kAllowDuplicateBindings};
 
 /// Add an empty entry point to the module, if no other entry points exist.
 /// @param module the module to transform
diff --git a/src/tint/lang/core/ir/transform/array_length_from_uniform.cc b/src/tint/lang/core/ir/transform/array_length_from_uniform.cc
index 33897bb..129b7da 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_uniform.cc
+++ b/src/tint/lang/core/ir/transform/array_length_from_uniform.cc
@@ -234,7 +234,8 @@
     Module& ir,
     BindingPoint ubo_binding,
     const std::unordered_map<BindingPoint, uint32_t>& bindpoint_to_size_index) {
-    auto validated = ValidateAndDumpIfNeeded(ir, "core.ArrayLengthFromUniform");
+    auto validated = ValidateAndDumpIfNeeded(ir, "core.ArrayLengthFromUniform",
+                                             kArrayLengthFromUniformCapabilities);
     if (validated != Success) {
         return validated.Failure();
     }
diff --git a/src/tint/lang/core/ir/transform/array_length_from_uniform.h b/src/tint/lang/core/ir/transform/array_length_from_uniform.h
index 0b1288e..e6e2cb1 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_uniform.h
+++ b/src/tint/lang/core/ir/transform/array_length_from_uniform.h
@@ -29,6 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_
 
 #include <unordered_map>
+#include "src/tint/lang/core/ir/validator.h"
 
 #include "src/tint/api/common/binding_point.h"
 #include "src/tint/utils/result.h"
@@ -40,6 +41,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kArrayLengthFromUniformCapabilities{Capability::kAllowDuplicateBindings};
+
 /// The result of running the ArrayLengthFromUniform transform.
 struct ArrayLengthFromUniformResult {
     /// `true` if the transformed module needs the storage buffer sizes UBO.
diff --git a/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc b/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc
index 5f26884..e3547bf 100644
--- a/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/array_length_from_uniform_fuzz.cc
@@ -52,4 +52,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::ArrayLengthFromUniformFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kArrayLengthFromUniformCapabilities);
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
index f6d6edd..f31bad7 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
@@ -182,7 +182,8 @@
 }  // namespace
 
 Result<SuccessType> Bgra8UnormPolyfill(Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.Bgra8UnormPolyfill");
+    auto result =
+        ValidateAndDumpIfNeeded(ir, "core.Bgra8UnormPolyfill", kBgra8UnormPolyfillCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
index e17454a..0a44980 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BGRA8UNORM_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BGRA8UNORM_POLYFILL_H_
 
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -37,6 +38,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kBgra8UnormPolyfillCapabilities{Capability::kAllowDuplicateBindings};
+
 /// Bgra8UnormPolyfill is a transform that changes the texel format of storage textures from
 /// bgra8unorm to rgba8unorm, inserting swizzles before and after texture accesses as necessary.
 /// @param module the module to transform
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc
index d6252ad..e4aa5be 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc
@@ -41,4 +41,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::Bgra8UnormPolyfillFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kBgra8UnormPolyfillCapabilities);
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.cc b/src/tint/lang/core/ir/transform/binary_polyfill.cc
index 7e5bc51..5780e7b 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.cc
@@ -201,7 +201,7 @@
 }  // namespace
 
 Result<SuccessType> BinaryPolyfill(Module& ir, const BinaryPolyfillConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.BinaryPolyfill");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.BinaryPolyfill", kBinaryPolyfillCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.h b/src/tint/lang/core/ir/transform/binary_polyfill.h
index 1af3933..a3621d2 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.h
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BINARY_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BINARY_POLYFILL_H_
 
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/reflection.h"
 #include "src/tint/utils/result.h"
 
@@ -38,6 +39,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kBinaryPolyfillCapabilities{Capability::kAllowDuplicateBindings};
+
 /// The set of polyfills that should be applied.
 struct BinaryPolyfillConfig {
     /// Should the RHS of a shift be masked to make it modulo the bit-width of the LHS?
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc
index 666bde9..0dc81b0 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill_fuzz.cc
@@ -43,4 +43,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::BinaryPolyfillFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kBinaryPolyfillCapabilities);
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.cc b/src/tint/lang/core/ir/transform/binding_remapper.cc
index ac6bb74..1852972 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper.cc
+++ b/src/tint/lang/core/ir/transform/binding_remapper.cc
@@ -72,7 +72,7 @@
 Result<SuccessType> BindingRemapper(
     Module& ir,
     const std::unordered_map<BindingPoint, BindingPoint>& binding_points) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.BindingRemapper");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.BindingRemapper", kBindingRemapperCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.h b/src/tint/lang/core/ir/transform/binding_remapper.h
index 83dbb08..efdc23c 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper.h
+++ b/src/tint/lang/core/ir/transform/binding_remapper.h
@@ -31,6 +31,7 @@
 #include <unordered_map>
 
 #include "src/tint/api/common/binding_point.h"
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -40,6 +41,18 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+///
+/// Note: BindingRemapper is the transform that introduces duplicate
+/// bindings, so in theory shouldn't need the capability to allow
+/// them. Except that in the MSL backend BindingRemapper is invoked multiple
+/// times, (FlattenBindings and Raise specifically), so may encounter IR with
+/// duplicate bindings when called the second time.
+// TODO(crbug.com/363031535): Remove kAllowDuplicateBindings when MSL no
+// longer needs FlattenBindings. binding_remapper_fuzz.cc will need to be
+// updated to have kAllowDuplicateBindings as a post-run capability.
+const Capabilities kBindingRemapperCapabilities{Capability::kAllowDuplicateBindings};
+
 /// BindingRemapper is a transform that remaps binding point indices and access controls.
 /// @param module the module to transform
 /// @param binding_points the remapping data
diff --git a/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc b/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc
index d38cc04..0dc244f 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/binding_remapper_fuzz.cc
@@ -44,4 +44,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::BindingRemapperFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kBindingRemapperCapabilities);
diff --git a/src/tint/lang/core/ir/transform/binding_remapper_test.cc b/src/tint/lang/core/ir/transform/binding_remapper_test.cc
index 63563c4..9e9d886 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper_test.cc
+++ b/src/tint/lang/core/ir/transform/binding_remapper_test.cc
@@ -37,7 +37,10 @@
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
 
-using IR_BindingRemapperTest = TransformTest;
+class IR_BindingRemapperTest : public TransformTest {
+  public:
+    IR_BindingRemapperTest() { capabilities = kBindingRemapperCapabilities; }
+};
 
 TEST_F(IR_BindingRemapperTest, NoModify_NoRemappings) {
     auto* buffer = b.Var("buffer", ty.ptr<uniform, i32>());
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs.cc b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
index 6dca4d6..075fc86 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs.cc
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
@@ -118,7 +118,8 @@
 }  // namespace
 
 Result<SuccessType> BlockDecoratedStructs(Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.BlockDecoratedStructs");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.BlockDecoratedStructs",
+                                          kBlockDecoratedStructsCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs.h b/src/tint/lang/core/ir/transform/block_decorated_structs.h
index 551ac66..232405f 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs.h
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BLOCK_DECORATED_STRUCTS_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BLOCK_DECORATED_STRUCTS_H_
 
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -37,6 +38,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kBlockDecoratedStructsCapabilities{Capability::kAllowDuplicateBindings};
+
 /// BlockDecoratedStructs is a transform that changes the store type of a buffer to be a special
 /// structure that is recognized as needing a block decoration in SPIR-V, potentially wrapping the
 /// existing store type in a new structure if necessary.
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc b/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
index 93c5fde..f5ac095 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
@@ -41,4 +41,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::BlockDecoratedStructsFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kBlockDecoratedStructsCapabilities);
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
index 9faa6ac..d576f96 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -1145,7 +1145,7 @@
 }  // namespace
 
 Result<SuccessType> BuiltinPolyfill(Module& ir, const BuiltinPolyfillConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.BuiltinPolyfill");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.BuiltinPolyfill", kBuiltinPolyfillCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.h b/src/tint/lang/core/ir/transform/builtin_polyfill.h
index 17c6206..94bbe0f 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.h
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_BUILTIN_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_BUILTIN_POLYFILL_H_
 
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/reflection.h"
 #include "src/tint/utils/result.h"
 
@@ -38,6 +39,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kBuiltinPolyfillCapabilities{Capability::kAllowDuplicateBindings};
+
 /// Enumerator of polyfill levels.
 enum class BuiltinPolyfillLevel {
     /// No polyfill needed, supported by the backend.
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc
index b37d755..6d5fb29 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc
@@ -43,4 +43,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::BuiltinPolyfillFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kBuiltinPolyfillCapabilities);
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill.cc b/src/tint/lang/core/ir/transform/conversion_polyfill.cc
index f1f17fb..193e38d 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill.cc
@@ -189,7 +189,8 @@
 }  // namespace
 
 Result<SuccessType> ConversionPolyfill(Module& ir, const ConversionPolyfillConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.ConversionPolyfill");
+    auto result =
+        ValidateAndDumpIfNeeded(ir, "core.ConversionPolyfill", kConversionPolyfillCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill.h b/src/tint/lang/core/ir/transform/conversion_polyfill.h
index ca09eb4..666e3b3 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill.h
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_CONVERSION_POLYFILL_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_CONVERSION_POLYFILL_H_
 
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/reflection.h"
 #include "src/tint/utils/result.h"
 
@@ -38,6 +39,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kConversionPolyfillCapabilities{Capability::kAllowDuplicateBindings};
+
 /// The set of polyfills that should be applied.
 struct ConversionPolyfillConfig {
     /// Should converting floating point values to integers be polyfilled?
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc
index 0003daf..32d7d45 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc
@@ -43,4 +43,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::ConversionPolyfillFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kConversionPolyfillCapabilities);
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper.h b/src/tint/lang/core/ir/transform/demote_to_helper.h
index dff14cc..90020fa 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper.h
+++ b/src/tint/lang/core/ir/transform/demote_to_helper.h
@@ -41,8 +41,7 @@
 /// The capabilities that the transform can support.
 const core::ir::Capabilities kDemoteToHelperCapabilities{
     core::ir::Capability::kAllowVectorElementPointer,
-    core::ir::Capability::kAllowClipDistancesOnF32,
-};
+    core::ir::Capability::kAllowClipDistancesOnF32, core::ir::Capability::kAllowDuplicateBindings};
 
 /// DemoteToHelper is a transform that emulates demote-to-helper semantics for discard instructions.
 ///
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.h b/src/tint/lang/core/ir/transform/direct_variable_access.h
index 77902eb..a57a8ed 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.h
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.h
@@ -41,8 +41,7 @@
 
 /// The capabilities that the transform can support.
 const core::ir::Capabilities kDirectVariableAccessCapabilities{
-    core::ir::Capability::kAllowClipDistancesOnF32,
-};
+    core::ir::Capability::kAllowClipDistancesOnF32, core::ir::Capability::kAllowDuplicateBindings};
 
 /// DirectVariableAccessOptions adjusts the behaviour of the transform.
 struct DirectVariableAccessOptions {
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
index 298563b..b68fbe1 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -609,7 +609,8 @@
 Result<SuccessType> MultiplanarExternalTexture(
     Module& ir,
     const tint::transform::multiplanar::BindingsMap& multiplanar_map) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.MultiplanarExternalTexture");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.MultiplanarExternalTexture",
+                                          kMultiplanarExternalTextureCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
index 6ed53c1..fd09675 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.h
@@ -29,6 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_MULTIPLANAR_EXTERNAL_TEXTURE_H_
 
 #include "src/tint/lang/core/ir/transform/multiplanar_options.h"
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -38,6 +39,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kMultiplanarExternalTextureCapabilities{Capability::kAllowDuplicateBindings};
+
 /// MultiplanarExternalTexture is a transform that splits texture_external bindings into two
 /// separate texture_2d<f32> bindings for two possible planes, along with a uniform buffer of
 /// parameters that describe how the texture should be sampled.
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
index b8e7b0d..537ceac 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
@@ -44,4 +44,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::MultiplanarExternalTextureFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kMultiplanarExternalTextureCapabilities);
diff --git a/src/tint/lang/core/ir/transform/preserve_padding.h b/src/tint/lang/core/ir/transform/preserve_padding.h
index c4f7c89..b9c2ed5 100644
--- a/src/tint/lang/core/ir/transform/preserve_padding.h
+++ b/src/tint/lang/core/ir/transform/preserve_padding.h
@@ -41,7 +41,7 @@
 /// The capabilities that the transform can support.
 const core::ir::Capabilities kPreservePaddingCapabilities{
     core::ir::Capability::kAllowHandleVarsWithoutBindings,
-};
+    core::ir::Capability::kAllowDuplicateBindings};
 
 /// PreservePadding is a transform that decomposes stores of whole structure and array types to
 /// preserve padding bytes.
diff --git a/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc b/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
index 7406c85..80bd749 100644
--- a/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
+++ b/src/tint/lang/core/ir/transform/prevent_infinite_loops.cc
@@ -116,7 +116,8 @@
 }  // namespace
 
 Result<SuccessType> PreventInfiniteLoops(Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.PreventInfiniteLoops");
+    auto result =
+        ValidateAndDumpIfNeeded(ir, "core.PreventInfiniteLoops", kPreventInfiniteLoopsCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/prevent_infinite_loops.h b/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
index 82fa555..292642e 100644
--- a/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
+++ b/src/tint/lang/core/ir/transform/prevent_infinite_loops.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_PREVENT_INFINITE_LOOPS_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_PREVENT_INFINITE_LOOPS_H_
 
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -37,6 +38,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kPreventInfiniteLoopsCapabilities{Capability::kAllowDuplicateBindings};
+
 /// PreventInfiniteLoops is a transform that injects an additional exit condition into loops that
 /// may be infinite, to prevent downstream compilers from making bad assumptions due to the
 /// undefined behavior of infinite loops.
diff --git a/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc b/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
index 445b2ca..5c02fb0 100644
--- a/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
+++ b/src/tint/lang/core/ir/transform/remove_continue_in_switch.cc
@@ -118,6 +118,7 @@
                                               core::ir::Capability::kAllowVectorElementPointer,
                                               core::ir::Capability::kAllowHandleVarsWithoutBindings,
                                               core::ir::Capability::kAllowClipDistancesOnF32,
+                                              core::ir::Capability::kAllowDuplicateBindings,
                                           });
     if (result != Success) {
         return result;
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args.h b/src/tint/lang/core/ir/transform/remove_terminator_args.h
index 89cc1d7..24832fb 100644
--- a/src/tint/lang/core/ir/transform/remove_terminator_args.h
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args.h
@@ -51,6 +51,7 @@
     core::ir::Capability::kAllowAnyLetType,
     core::ir::Capability::kAllowModuleScopeLets,
     core::ir::Capability::kAllowWorkspacePointerInputToEntryPoint,
+    core::ir::Capability::kAllowDuplicateBindings,
 };
 
 /// RemoveTerminatorArgs is a transform that removes all arguments from terminator instructions and
diff --git a/src/tint/lang/core/ir/transform/rename_conflicts.h b/src/tint/lang/core/ir/transform/rename_conflicts.h
index 8787904..dbd83c7 100644
--- a/src/tint/lang/core/ir/transform/rename_conflicts.h
+++ b/src/tint/lang/core/ir/transform/rename_conflicts.h
@@ -51,6 +51,7 @@
     core::ir::Capability::kAllowAnyLetType,
     core::ir::Capability::kAllowModuleScopeLets,
     core::ir::Capability::kAllowWorkspacePointerInputToEntryPoint,
+    core::ir::Capability::kAllowDuplicateBindings,
 };
 
 /// RenameConflicts is a transform that renames declarations which prevent identifiers from
diff --git a/src/tint/lang/core/ir/transform/robustness.cc b/src/tint/lang/core/ir/transform/robustness.cc
index 9d716c9..bebbb22 100644
--- a/src/tint/lang/core/ir/transform/robustness.cc
+++ b/src/tint/lang/core/ir/transform/robustness.cc
@@ -568,7 +568,7 @@
 }  // namespace
 
 Result<SuccessType> Robustness(Module& ir, const RobustnessConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.Robustness");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.Robustness", kRobustnessCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/robustness.h b/src/tint/lang/core/ir/transform/robustness.h
index 2f0044a..db1755e 100644
--- a/src/tint/lang/core/ir/transform/robustness.h
+++ b/src/tint/lang/core/ir/transform/robustness.h
@@ -29,6 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_ROBUSTNESS_H_
 
 #include <unordered_set>
+#include "src/tint/lang/core/ir/validator.h"
 
 #include "src/tint/api/common/binding_point.h"
 #include "src/tint/utils/reflection.h"
@@ -41,6 +42,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kRobustnessCapabilities{Capability::kAllowDuplicateBindings};
+
 /// Configuration options that control when to clamp accesses.
 struct RobustnessConfig {
     /// Should non-pointer accesses be clamped?
diff --git a/src/tint/lang/core/ir/transform/robustness_fuzz.cc b/src/tint/lang/core/ir/transform/robustness_fuzz.cc
index b553293..d8a740a 100644
--- a/src/tint/lang/core/ir/transform/robustness_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/robustness_fuzz.cc
@@ -42,4 +42,5 @@
 }  // namespace
 }  // namespace tint::core::ir::transform
 
-TINT_IR_MODULE_FUZZER(tint::core::ir::transform::RobustnessFuzzer, tint::core::ir::Capabilities{});
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::RobustnessFuzzer,
+                      tint::core::ir::transform::kRobustnessCapabilities);
diff --git a/src/tint/lang/core/ir/transform/std140.h b/src/tint/lang/core/ir/transform/std140.h
index c63d4e7..4d0cc42 100644
--- a/src/tint/lang/core/ir/transform/std140.h
+++ b/src/tint/lang/core/ir/transform/std140.h
@@ -42,6 +42,7 @@
 const core::ir::Capabilities kStd140Capabilities{
     core::ir::Capability::kAllowHandleVarsWithoutBindings,
     core::ir::Capability::kAllowAnyInputAttachmentIndexType,
+    core::ir::Capability::kAllowDuplicateBindings,
 };
 
 /// Std140 is a transform that rewrites matrix types in the uniform address space to conform to
diff --git a/src/tint/lang/core/ir/transform/value_to_let.h b/src/tint/lang/core/ir/transform/value_to_let.h
index f60921e..d44f0d7 100644
--- a/src/tint/lang/core/ir/transform/value_to_let.h
+++ b/src/tint/lang/core/ir/transform/value_to_let.h
@@ -51,6 +51,7 @@
     core::ir::Capability::kAllowAnyLetType,
     core::ir::Capability::kAllowWorkspacePointerInputToEntryPoint,
     core::ir::Capability::kAllowModuleScopeLets,
+    core::ir::Capability::kAllowDuplicateBindings,
 };
 
 /// Configuration for ValueToLet transform.
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
index 6954ffe..2d0e376 100644
--- a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h
@@ -43,6 +43,7 @@
     core::ir::Capability::kAllowVectorElementPointer,
     core::ir::Capability::kAllowHandleVarsWithoutBindings,
     core::ir::Capability::kAllowClipDistancesOnF32,
+    core::ir::Capability::kAllowDuplicateBindings,
 };
 
 /// VectorizeScalarMatrixConstructors is a transform that replaces construct instructions that
diff --git a/src/tint/lang/core/ir/transform/vertex_pulling.cc b/src/tint/lang/core/ir/transform/vertex_pulling.cc
index d8d8938..09f36c8 100644
--- a/src/tint/lang/core/ir/transform/vertex_pulling.cc
+++ b/src/tint/lang/core/ir/transform/vertex_pulling.cc
@@ -684,7 +684,7 @@
 }  // namespace
 
 Result<SuccessType> VertexPulling(core::ir::Module& ir, const VertexPullingConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.VertexPulling");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.VertexPulling", kVertexPullingCapabilities);
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/core/ir/transform/vertex_pulling.h b/src/tint/lang/core/ir/transform/vertex_pulling.h
index fe6fe51..34fd6b7 100644
--- a/src/tint/lang/core/ir/transform/vertex_pulling.h
+++ b/src/tint/lang/core/ir/transform/vertex_pulling.h
@@ -29,6 +29,7 @@
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_VERTEX_PULLING_H_
 
 #include "src/tint/api/common/vertex_pulling_config.h"
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -38,6 +39,11 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+// TODO(crbug.com/363031535): Remove kAllowDuplicateBindings when MSL no
+// longer needs FlattenBindings.
+const Capabilities kVertexPullingCapabilities{Capability::kAllowDuplicateBindings};
+
 /// This transform replaces vertex shader inputs with storage buffers, so that we can apply
 /// robustness to the vertex input accesses.
 ///
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
index aecbde3..fcb0840 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
@@ -306,7 +306,8 @@
 }  // namespace
 
 Result<SuccessType> ZeroInitWorkgroupMemory(Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "core.ZeroInitWorkgroupMemory");
+    auto result = ValidateAndDumpIfNeeded(ir, "core.ZeroInitWorkgroupMemory",
+                                          kZeroInitWorkgroupMemoryCapabilities);
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h
index f73df08..01feb70 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_ZERO_INIT_WORKGROUP_MEMORY_H_
 
+#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -37,6 +38,9 @@
 
 namespace tint::core::ir::transform {
 
+/// The capabilities that the transform can support.
+const Capabilities kZeroInitWorkgroupMemoryCapabilities{Capability::kAllowDuplicateBindings};
+
 /// ZeroInitWorkgroupMemory is a transform that injects code at the top of each entry point to
 /// zero-initialize workgroup memory used by that entry point.
 /// @param module the module to transform
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
index 970217d..b197f6b 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
@@ -41,4 +41,4 @@
 }  // namespace tint::core::ir::transform
 
 TINT_IR_MODULE_FUZZER(tint::core::ir::transform::ZeroInitWorkgroupMemoryFuzzer,
-                      tint::core::ir::Capabilities{});
+                      tint::core::ir::transform::kZeroInitWorkgroupMemoryCapabilities);
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 58bfacb..b3ed46a 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -1385,7 +1385,6 @@
     core::ir::ReferencedModuleVars<const Module> referenced_module_vars_;
     Hashset<OverrideId, 8> seen_override_ids_;
     Hashset<std::string, 4> entry_point_names_;
-
     Hashset<ValidatedType, 16> validated_types_{};
 };
 
@@ -2251,7 +2250,18 @@
             func, CheckFrontFacingIfBoolFunc<Function>("entry point returns can not be 'bool'"),
             CheckFrontFacingIfBoolFunc<Function>("entry point return members can not be 'bool'"));
 
+        Hashset<BindingPoint, 4> binding_points{};
+
         for (auto var : referenced_module_vars_.TransitiveReferences(func)) {
+            if (!capabilities_.Contains(Capability::kAllowDuplicateBindings) &&
+                var->BindingPoint().has_value()) {
+                auto bp = var->BindingPoint().value();
+                if (!binding_points.Add(bp)) {
+                    AddError(var) << "found non-unique binding point, " << bp
+                                  << ", being referenced in entry point, " << NameOf(func);
+                }
+            }
+
             const auto* mv = var->Result()->Type()->As<core::type::MemoryView>();
             const auto* ty = var->Result()->Type()->UnwrapPtrOrRef();
             const auto attr = var->Attributes();
diff --git a/src/tint/lang/core/ir/validator.h b/src/tint/lang/core/ir/validator.h
index b7efa1d..919aeb0 100644
--- a/src/tint/lang/core/ir/validator.h
+++ b/src/tint/lang/core/ir/validator.h
@@ -70,6 +70,9 @@
     /// Allows workgroup address space pointers as entry point inputs. Used by
     /// the MSL backend.
     kAllowWorkspacePointerInputToEntryPoint,
+    /// Allows binding points to be non-unique. Used after BindingRemapper is
+    /// invoked by MSL & GLSL backends.
+    kAllowDuplicateBindings,
 };
 
 /// Capabilities is a set of Capability
diff --git a/src/tint/lang/core/ir/validator_value_test.cc b/src/tint/lang/core/ir/validator_value_test.cc
index 4ee3573..31c283c 100644
--- a/src/tint/lang/core/ir/validator_value_test.cc
+++ b/src/tint/lang/core/ir/validator_value_test.cc
@@ -521,6 +521,110 @@
 )")) << res.Failure();
 }
 
+TEST_F(IR_ValidatorTest, Var_DuplicateBindingPoints_NeitherReferenced) {
+    auto* var_a = b.Var<uniform, f32>();
+    var_a->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_a);
+
+    auto* var_b = b.Var<uniform, i32>();
+    var_b->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_b);
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, Var_DuplicateBindingPoints_OnlyOneReferenced) {
+    auto* var_a = b.Var<uniform, f32>();
+    var_a->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_a);
+
+    auto* var_b = b.Var<uniform, i32>();
+    var_b->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_b);
+
+    auto* f = FragmentEntryPoint();
+    b.Append(f->Block(), [&] {
+        b.Let(var_a->Result());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, Var_DuplicateBindingPoints_ReferencedInDifferentFunctions) {
+    auto* var_a = b.Var<uniform, f32>();
+    var_a->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_a);
+
+    auto* var_b = b.Var<uniform, i32>();
+    var_b->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_b);
+
+    auto* func_a = FragmentEntryPoint("func_a");
+    b.Append(func_a->Block(), [&] {
+        b.Let(var_a->Result());
+        b.Return(func_a);
+    });
+
+    auto* func_b = FragmentEntryPoint("func_b");
+    b.Append(func_b->Block(), [&] {
+        b.Let(var_a->Result());
+        b.Return(func_b);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, Var_DuplicateBindingPoints_BothReferenced) {
+    auto* var_a = b.Var<uniform, f32>();
+    var_a->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_a);
+
+    auto* var_b = b.Var<uniform, i32>();
+    var_b->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_b);
+
+    auto* f = FragmentEntryPoint();
+    b.Append(f->Block(), [&] {
+        b.Let(var_a->Result());
+        b.Let(var_b->Result());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(
+        res.Failure().reason,
+        testing::HasSubstr(
+            R"(:3:32 error: var: found non-unique binding point, [group: 1, binding: 2], being referenced in entry point, %f
+  %2:ptr<uniform, i32, read> = var undef @binding_point(1, 2)
+                               ^^^
+)")) << res.Failure();
+}
+
+TEST_F(IR_ValidatorTest, Var_DuplicateBindingPoints_CapabilityOverride) {
+    auto* var_a = b.Var<uniform, f32>();
+    var_a->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_a);
+
+    auto* var_b = b.Var<uniform, i32>();
+    var_b->SetBindingPoint(1, 2);
+    mod.root_block->Append(var_b);
+
+    auto* f = FragmentEntryPoint();
+    b.Append(f->Block(), [&] {
+        b.Let(var_a->Result());
+        b.Let(var_b->Result());
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod, Capabilities{Capability::kAllowDuplicateBindings});
+    ASSERT_EQ(res, Success);
+}
+
 TEST_F(IR_ValidatorTest, Var_MultipleIOAnnotations) {
     auto* v = b.Var<AddressSpace::kIn, vec4<f32>>();
     v->SetBuiltin(BuiltinValue::kPosition);
diff --git a/src/tint/lang/glsl/writer/builtin_test.cc b/src/tint/lang/glsl/writer/builtin_test.cc
index 2bca6ab..bed60c8 100644
--- a/src/tint/lang/glsl/writer/builtin_test.cc
+++ b/src/tint/lang/glsl/writer/builtin_test.cc
@@ -877,6 +877,7 @@
     });
 
     Options opts{};
+    opts.bindings.texture_builtins_from_uniform.ubo_binding = {0, 1};
     opts.bindings.texture_builtins_from_uniform.ubo_bindingpoint_ordering = {{0, 0}};
     ASSERT_TRUE(Generate(opts)) << err_ << output_.glsl;
     EXPECT_EQ(output_.glsl, GlslHeader() + R"(precision highp float;
@@ -887,7 +888,7 @@
   uint tint_builtin_value_0;
 };
 
-layout(binding = 0, std140)
+layout(binding = 1, std140)
 uniform f_tint_symbol_ubo {
   TintTextureUniformData inner;
 } v_1;
diff --git a/src/tint/lang/glsl/writer/printer/printer.cc b/src/tint/lang/glsl/writer/printer/printer.cc
index 6cb1fff..cbde070 100644
--- a/src/tint/lang/glsl/writer/printer/printer.cc
+++ b/src/tint/lang/glsl/writer/printer/printer.cc
@@ -128,7 +128,8 @@
     tint::Result<Output> Generate() {
         auto valid = core::ir::ValidateAndDumpIfNeeded(
             ir_, "glsl.Printer",
-            core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings});
+            core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings,
+                                   core::ir::Capability::kAllowDuplicateBindings});
         if (valid != Success) {
             return std::move(valid.Failure());
         }
diff --git a/src/tint/lang/glsl/writer/raise/binary_polyfill.cc b/src/tint/lang/glsl/writer/raise/binary_polyfill.cc
index 7e4f0a3..988071e 100644
--- a/src/tint/lang/glsl/writer/raise/binary_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/binary_polyfill.cc
@@ -216,7 +216,9 @@
 }  // namespace
 
 Result<SuccessType> BinaryPolyfill(core::ir::Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "glsl.BinaryPolyfill");
+    auto result = ValidateAndDumpIfNeeded(
+        ir, "glsl.BinaryPolyfill",
+        core::ir::Capabilities{core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
index df80ab9..93ee29b 100644
--- a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
@@ -278,7 +278,8 @@
 Result<SuccessType> BitcastPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(
         ir, "glsl.BitcastPolyfill",
-        core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings});
+        core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings,
+                               core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc b/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc
index 9cd25f4..2478f76 100644
--- a/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/builtin_polyfill.cc
@@ -526,7 +526,9 @@
 }  // namespace
 
 Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "glsl.BuiltinPolyfill");
+    auto result = ValidateAndDumpIfNeeded(
+        ir, "glsl.BuiltinPolyfill",
+        core::ir::Capabilities{core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/glsl/writer/raise/offset_first_index.cc b/src/tint/lang/glsl/writer/raise/offset_first_index.cc
index cfc5897..ec785fd 100644
--- a/src/tint/lang/glsl/writer/raise/offset_first_index.cc
+++ b/src/tint/lang/glsl/writer/raise/offset_first_index.cc
@@ -115,6 +115,7 @@
     auto result = ValidateAndDumpIfNeeded(ir, "glsl.OffsetFirstIndex",
                                           core::ir::Capabilities{
                                               core::ir::Capability::kAllowHandleVarsWithoutBindings,
+                                              core::ir::Capability::kAllowDuplicateBindings,
                                           });
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/glsl/writer/raise/raise.cc b/src/tint/lang/glsl/writer/raise/raise.cc
index 69e3f0b..eeb9feb 100644
--- a/src/tint/lang/glsl/writer/raise/raise.cc
+++ b/src/tint/lang/glsl/writer/raise/raise.cc
@@ -126,7 +126,7 @@
     RemapperData remapper_data{};
     PopulateBindingInfo(options, remapper_data, multiplanar_map);
     RUN_TRANSFORM(core::ir::transform::BindingRemapper, module, remapper_data);
-
+    // Capability::kAllowDuplicateBindings needed after BindingRemapper
     {
         core::ir::transform::BinaryPolyfillConfig binary_polyfills{};
         binary_polyfills.int_div_mod = !options.disable_polyfill_integer_div_mod;
diff --git a/src/tint/lang/glsl/writer/raise/shader_io.cc b/src/tint/lang/glsl/writer/raise/shader_io.cc
index 9b3d99c..9709e48 100644
--- a/src/tint/lang/glsl/writer/raise/shader_io.cc
+++ b/src/tint/lang/glsl/writer/raise/shader_io.cc
@@ -239,7 +239,8 @@
 Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
     auto result = ValidateAndDumpIfNeeded(
         ir, "glsl.ShaderIO",
-        core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings});
+        core::ir::Capabilities{core::ir::Capability::kAllowHandleVarsWithoutBindings,
+                               core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/glsl/writer/raise/texture_polyfill.cc b/src/tint/lang/glsl/writer/raise/texture_polyfill.cc
index a3cfd3b..7430896 100644
--- a/src/tint/lang/glsl/writer/raise/texture_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/texture_polyfill.cc
@@ -1228,7 +1228,9 @@
 }  // namespace
 
 Result<SuccessType> TexturePolyfill(core::ir::Module& ir, const TexturePolyfillConfig& cfg) {
-    auto result = ValidateAndDumpIfNeeded(ir, "glsl.TexturePolyfill");
+    auto result = ValidateAndDumpIfNeeded(
+        ir, "glsl.TexturePolyfill",
+        core::ir::Capabilities{core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/msl/writer/raise/argument_buffers.cc b/src/tint/lang/msl/writer/raise/argument_buffers.cc
index b37028c..8166a2d 100644
--- a/src/tint/lang/msl/writer/raise/argument_buffers.cc
+++ b/src/tint/lang/msl/writer/raise/argument_buffers.cc
@@ -308,7 +308,9 @@
 }  // namespace
 
 Result<SuccessType> ArgumentBuffers(core::ir::Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "msl.ArgumentBuffers");
+    auto result = ValidateAndDumpIfNeeded(
+        ir, "msl.ArgumentBuffers",
+        tint::core::ir::Capabilities{tint::core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/msl/writer/raise/module_scope_vars.cc b/src/tint/lang/msl/writer/raise/module_scope_vars.cc
index 87fe418..113606b 100644
--- a/src/tint/lang/msl/writer/raise/module_scope_vars.cc
+++ b/src/tint/lang/msl/writer/raise/module_scope_vars.cc
@@ -346,6 +346,7 @@
                                 core::ir::Capabilities{
                                     core::ir::Capability::kAllowPointersAndHandlesInStructures,
                                     core::ir::Capability::kAllowWorkspacePointerInputToEntryPoint,
+                                    core::ir::Capability::kAllowDuplicateBindings,
                                 });
     if (result != Success) {
         return result.Failure();
diff --git a/src/tint/lang/msl/writer/raise/packed_vec3.cc b/src/tint/lang/msl/writer/raise/packed_vec3.cc
index d382ab8..b58799c 100644
--- a/src/tint/lang/msl/writer/raise/packed_vec3.cc
+++ b/src/tint/lang/msl/writer/raise/packed_vec3.cc
@@ -640,7 +640,9 @@
 }  // namespace
 
 Result<SuccessType> PackedVec3(core::ir::Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "msl.PackedVec3");
+    auto result = ValidateAndDumpIfNeeded(
+        ir, "msl.PackedVec3",
+        tint::core::ir::Capabilities{tint::core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result.Failure();
     }
diff --git a/src/tint/lang/msl/writer/raise/shader_io.cc b/src/tint/lang/msl/writer/raise/shader_io.cc
index 8c53bfd..e1c715d 100644
--- a/src/tint/lang/msl/writer/raise/shader_io.cc
+++ b/src/tint/lang/msl/writer/raise/shader_io.cc
@@ -237,7 +237,9 @@
 }  // namespace
 
 Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
-    auto result = ValidateAndDumpIfNeeded(ir, "msl.ShaderIO");
+    auto result = ValidateAndDumpIfNeeded(
+        ir, "msl.ShaderIO",
+        tint::core::ir::Capabilities{tint::core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result;
     }
diff --git a/src/tint/lang/msl/writer/raise/simd_ballot.cc b/src/tint/lang/msl/writer/raise/simd_ballot.cc
index 27283c6..ea16af5 100644
--- a/src/tint/lang/msl/writer/raise/simd_ballot.cc
+++ b/src/tint/lang/msl/writer/raise/simd_ballot.cc
@@ -158,7 +158,9 @@
 }  // namespace
 
 Result<SuccessType> SimdBallot(core::ir::Module& ir) {
-    auto result = ValidateAndDumpIfNeeded(ir, "msl.SimdBallot");
+    auto result = ValidateAndDumpIfNeeded(
+        ir, "msl.SimdBallot",
+        tint::core::ir::Capabilities{tint::core::ir::Capability::kAllowDuplicateBindings});
     if (result != Success) {
         return result.Failure();
     }