[tint][ir][val] Improve checks regarding binding points

- Refactors related code into common utility function
- Requires binding points for resource variables/param
- Rejects binding points on non-resource variables/params
- Reject binding point on non-entry point input parameters

Fixes: 377850460
Change-Id: I041088d42227f7344101bfffd9a52477509907ad
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/214354
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
index 3cca4ac..d5ded41 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_test.cc
@@ -2531,7 +2531,6 @@
     b.Append(b.ir.root_block,
              [&] {  //
                  input = b.Var("U", ty.ptr<workgroup>(T));
-                 input->SetBindingPoint(0, 0);
              });
 
     auto* f2 = b.Function("f2", T3);
@@ -2566,7 +2565,7 @@
 
     auto* src = R"(
 $B1: {  # root
-  %U:ptr<workgroup, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0)
+  %U:ptr<workgroup, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
 }
 
 %f2 = func(%p:ptr<workgroup, array<vec4<i32>, 5>, read_write>):vec4<i32> {
@@ -2602,7 +2601,7 @@
 
     auto* expect = R"(
 $B1: {  # root
-  %U:ptr<workgroup, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var @binding_point(0, 0)
+  %U:ptr<workgroup, array<array<array<vec4<i32>, 5>, 5>, 5>, read_write> = var
 }
 
 %f2 = func(%p_indices:array<u32, 2>):vec4<i32> {
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 3abb1dc..7655fbc 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -992,6 +992,17 @@
     /// @param var the var to validate
     void CheckVar(const Var* var);
 
+    /// Validates binding_point usage for pointers
+    /// @param binding_point the binding information associated with pointer
+    /// @param address_space the address space of pointer
+    /// @param target_str string to insert in error message describing what has a binding_point,
+    /// defaults to 'variable'
+    /// @returns Success if a valid usage, or reason for invalidity in Failure
+    Result<SuccessType, std::string> ValidateBindingPoint(
+        const std::optional<struct BindingPoint>& binding_point,
+        AddressSpace address_space,
+        const std::string& target_str = "variable");
+
     /// Validates the given let
     /// @param l the let to validate
     void CheckLet(const Let* l);
@@ -1884,6 +1895,29 @@
                            "entry point params can only be a bool for fragment shaders"));
         }
 
+        AddressSpace address_space = AddressSpace::kUndefined;
+        auto* mv = param->Type()->As<type::MemoryView>();
+        if (mv) {
+            address_space = mv->AddressSpace();
+        } else {
+            // ModuleScopeVars transform in MSL backends unwraps pointers to handles
+            if (param->Type()->IsAnyOf<type::Texture, type::Sampler>()) {
+                address_space = AddressSpace::kHandle;
+            }
+        }
+
+        if (func->Stage() != Function::PipelineStage::kUndefined) {
+            auto result = ValidateBindingPoint(param->BindingPoint(), address_space, "input param");
+            if (result != Success) {
+                AddError(param) << result.Failure();
+            }
+        } else {
+            if (param->BindingPoint().has_value()) {
+                AddError(param)
+                    << "input param to non-entry point function has a binding point set";
+            }
+        }
+
         scope_stack_.Add(param);
     }
 
@@ -2275,23 +2309,12 @@
         return;
     }
 
-    // Check that only resource variables have @group and @binding set
-    switch (mv->AddressSpace()) {
-        case AddressSpace::kHandle:
-            if (!capabilities_.Contains(Capability::kAllowHandleVarsWithoutBindings)) {
-                if (!var->BindingPoint().has_value()) {
-                    AddError(var) << "resource variable missing binding points";
-                }
-            }
-            break;
-        case AddressSpace::kStorage:
-        case AddressSpace::kUniform:
-            if (!var->BindingPoint().has_value()) {
-                AddError(var) << "resource variable missing binding points";
-            }
-            break;
-        default:
-            break;
+    {
+        auto result = ValidateBindingPoint(var->BindingPoint(), mv->AddressSpace());
+        if (result != Success) {
+            AddError(var) << result.Failure();
+            return;
+        }
     }
 
     // Check that non-handle variables don't have @input_attachment_index set
@@ -2321,6 +2344,33 @@
     }
 }
 
+Result<SuccessType, std::string> Validator::ValidateBindingPoint(
+    const std::optional<struct BindingPoint>& binding_point,
+    AddressSpace address_space,
+    const std::string& target_str) {
+    switch (address_space) {
+        case AddressSpace::kHandle:
+            if (!capabilities_.Contains(Capability::kAllowHandleVarsWithoutBindings)) {
+                if (!binding_point.has_value()) {
+                    return "a resource " + target_str + " is missing binding point";
+                }
+            }
+            break;
+        case AddressSpace::kStorage:
+        case AddressSpace::kUniform:
+            if (!binding_point.has_value()) {
+                return "a resource " + target_str + " is missing binding point";
+            }
+            break;
+        default:
+            if (binding_point.has_value()) {
+                return "a non-resource " + target_str + " has binding point";
+            }
+            break;
+    }
+    return Success;
+}
+
 void Validator::CheckLet(const Let* l) {
     if (!CheckResultsAndOperands(l, Let::kNumResults, Let::kNumOperands)) {
         return;
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 81bc844..55cb1d2 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -661,6 +661,30 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Function_Param_BindingPointWithoutCapability) {
+    auto* f = b.Function("my_func", ty.void_());
+    auto* p = b.FunctionParam("my_param", ty.ptr<uniform, i32>());
+    p->SetBindingPoint(0, 0);
+    f->SetParams({p});
+
+    b.Append(f->Block(), [&] { b.Return(f); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:1:17 error: input param to non-entry point function has a binding point set
+%my_func = func(%my_param:ptr<uniform, i32, read> [@binding_point(0, 0)]):void {
+                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func(%my_param:ptr<uniform, i32, read> [@binding_point(0, 0)]):void {
+  $B1: {
+    ret
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Function_Return_BothLocationAndBuiltin) {
     auto* f = VertexEntryPoint("my_func");
     IOAttributes attr;
@@ -5089,7 +5113,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:2:31 error: var: resource variable missing binding points
+              R"(:2:31 error: var: a resource variable is missing binding point
   %1:ptr<handle, i32, read> = var
                               ^^^
 
@@ -5112,7 +5136,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:2:38 error: var: resource variable missing binding points
+              R"(:2:38 error: var: a resource variable is missing binding point
   %1:ptr<storage, i32, read_write> = var
                                      ^^^
 
@@ -5135,7 +5159,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:2:32 error: var: resource variable missing binding points
+              R"(:2:32 error: var: a resource variable is missing binding point
   %1:ptr<uniform, i32, read> = var
                                ^^^
 
@@ -5151,13 +5175,36 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Var_NonResourceWithBindingPoint) {
+    auto* v = b.Var(ty.ptr<private_, i32>());
+    v->SetBindingPoint(0, 0);
+    mod.root_block->Append(v);
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:2:38 error: var: a non-resource variable has binding point
+  %1:ptr<private, i32, read_write> = var @binding_point(0, 0)
+                                     ^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %1:ptr<private, i32, read_write> = var @binding_point(0, 0)
+}
+
+)");
+}
+
 TEST_F(IR_ValidatorTest, Var_IOBothLocationAndBuiltin) {
     auto* v = b.Var<AddressSpace::kIn, vec4<f32>>();
     IOAttributes attr;
     attr.builtin = BuiltinValue::kPosition;
     attr.location = 0;
     v->SetAttributes(attr);
-    v->SetBindingPoint(0, 0);
     mod.root_block->Append(v);
 
     auto res = ir::Validate(mod);
@@ -5165,7 +5212,7 @@
     EXPECT_EQ(
         res.Failure().reason.Str(),
         R"(:2:35 error: var: a builtin and location cannot be both declared for a module scope var
-  %1:ptr<__in, vec4<f32>, read> = var @binding_point(0, 0) @location(0) @builtin(position)
+  %1:ptr<__in, vec4<f32>, read> = var @location(0) @builtin(position)
                                   ^^^
 
 :1:1 note: in block
@@ -5174,7 +5221,7 @@
 
 note: # Disassembly
 $B1: {  # root
-  %1:ptr<__in, vec4<f32>, read> = var @binding_point(0, 0) @location(0) @builtin(position)
+  %1:ptr<__in, vec4<f32>, read> = var @location(0) @builtin(position)
 }
 
 )");
@@ -5190,7 +5237,6 @@
                                                    {mod.symbols.New("a"), ty.f32(), attr},
                                                });
     auto* v = b.Var(ty.ptr(AddressSpace::kOut, str_ty, read_write));
-    v->SetBindingPoint(0, 0);
     mod.root_block->Append(v);
 
     auto res = ir::Validate(mod);
@@ -5198,7 +5244,7 @@
     EXPECT_EQ(
         res.Failure().reason.Str(),
         R"(:6:41 error: var: a builtin and location cannot be both declared for a module scope var struct member
-  %1:ptr<__out, MyStruct, read_write> = var @binding_point(0, 0)
+  %1:ptr<__out, MyStruct, read_write> = var
                                         ^^^
 
 :5:1 note: in block
@@ -5211,7 +5257,7 @@
 }
 
 $B1: {  # root
-  %1:ptr<__out, MyStruct, read_write> = var @binding_point(0, 0)
+  %1:ptr<__out, MyStruct, read_write> = var
 }
 
 )");
diff --git a/src/tint/lang/glsl/writer/raise/builtin_polyfill_test.cc b/src/tint/lang/glsl/writer/raise/builtin_polyfill_test.cc
index 8a19095..c33a0c6 100644
--- a/src/tint/lang/glsl/writer/raise/builtin_polyfill_test.cc
+++ b/src/tint/lang/glsl/writer/raise/builtin_polyfill_test.cc
@@ -202,7 +202,6 @@
 
 TEST_F(GlslWriter_BuiltinPolyfillTest, AtomicCompareExchangeWeak) {
     auto* var = b.Var("v", workgroup, ty.atomic<i32>(), core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.ComputeFunction("foo");
@@ -219,7 +218,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -239,7 +238,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -261,7 +260,6 @@
 
 TEST_F(GlslWriter_BuiltinPolyfillTest, AtomicSub) {
     auto* var = b.Var("v", workgroup, ty.atomic<i32>(), core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.ComputeFunction("foo");
@@ -272,7 +270,7 @@
 
     auto* src = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -287,7 +285,7 @@
 
     auto* expect = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -306,7 +304,6 @@
 
 TEST_F(GlslWriter_BuiltinPolyfillTest, AtomicSub_u32) {
     auto* var = b.Var("v", workgroup, ty.atomic<u32>(), core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.ComputeFunction("foo");
@@ -317,7 +314,7 @@
 
     auto* src = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<u32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<u32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -332,7 +329,7 @@
 
     auto* expect = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<u32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<u32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -350,7 +347,6 @@
 
 TEST_F(GlslWriter_BuiltinPolyfillTest, AtomicLoad) {
     auto* var = b.Var("v", workgroup, ty.atomic<i32>(), core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.ComputeFunction("foo");
@@ -361,7 +357,7 @@
 
     auto* src = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
@@ -376,7 +372,7 @@
 
     auto* expect = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
diff --git a/src/tint/lang/glsl/writer/var_and_let_test.cc b/src/tint/lang/glsl/writer/var_and_let_test.cc
index bdf9288..c3605c3 100644
--- a/src/tint/lang/glsl/writer/var_and_let_test.cc
+++ b/src/tint/lang/glsl/writer/var_and_let_test.cc
@@ -123,7 +123,6 @@
         core::IOAttributes attrs = {};
         attrs.builtin = core::BuiltinValue::kLocalInvocationIndex;
         v->SetAttributes(attrs);
-        v->SetBindingPoint(1, 2);
     });
 
     ASSERT_TRUE(Generate()) << err_ << output_.glsl;
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
index 1febf2b..65b2f52 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill_test.cc
@@ -5775,7 +5775,6 @@
 TEST_P(HlslBuiltinPolyfillWorkgroupAtomic, Access) {
     auto param = GetParam();
     auto* var = b.Var("v", workgroup, ty.atomic<i32>(), core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -5786,7 +5785,7 @@
 
     std::string src = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -5802,7 +5801,7 @@
 
     std::string expect = R"(
 $B1: {  # root
-  %v:ptr<workgroup, atomic<i32>, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, atomic<i32>, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -5840,7 +5839,6 @@
                                                 });
 
     auto* var = b.Var("v", workgroup, sb, core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -5858,7 +5856,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -5879,7 +5877,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -5903,7 +5901,6 @@
                                                 });
 
     auto* var = b.Var("v", workgroup, sb, core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -5921,7 +5918,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -5943,7 +5940,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -5969,7 +5966,6 @@
                                                 });
 
     auto* var = b.Var("v", workgroup, sb, core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -5989,7 +5985,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -6014,7 +6010,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -6047,7 +6043,6 @@
                                                 });
 
     auto* var = b.Var("v", workgroup, sb, core::Access::kReadWrite);
-    var->SetBindingPoint(0, 0);
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -6072,7 +6067,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
@@ -6099,7 +6094,7 @@
 }
 
 $B1: {  # root
-  %v:ptr<workgroup, SB, read_write> = var @binding_point(0, 0)
+  %v:ptr<workgroup, SB, read_write> = var
 }
 
 %foo = @fragment func():void {
diff --git a/src/tint/lang/hlsl/writer/var_let_test.cc b/src/tint/lang/hlsl/writer/var_let_test.cc
index a598027..ff18836 100644
--- a/src/tint/lang/hlsl/writer/var_let_test.cc
+++ b/src/tint/lang/hlsl/writer/var_let_test.cc
@@ -504,7 +504,6 @@
 
 TEST_F(HlslWriterTest, VarPrivate) {
     auto* s = b.Var("u", ty.ptr<private_>(ty.vec4<f32>()));
-    s->SetBindingPoint(2, 1);
 
     b.ir.root_block->Append(s);
 
@@ -520,7 +519,6 @@
 
 TEST_F(HlslWriterTest, VarWorkgroup) {
     auto* s = b.Var("u", ty.ptr<workgroup>(ty.vec4<f32>()));
-    s->SetBindingPoint(2, 1);
 
     b.ir.root_block->Append(s);
 
diff --git a/src/tint/lang/spirv/writer/var_test.cc b/src/tint/lang/spirv/writer/var_test.cc
index 3a9227a..2e8b159 100644
--- a/src/tint/lang/spirv/writer/var_test.cc
+++ b/src/tint/lang/spirv/writer/var_test.cc
@@ -304,7 +304,6 @@
 
 TEST_F(SpirvWriterTest, StorageVar_Workgroup_WithVulkan) {
     auto* v = b.Var("v", ty.ptr<workgroup, i32, read_write>());
-    v->SetBindingPoint(0, 0);
     mod.root_block->Append(v);
 
     auto* func = b.ComputeFunction("foo");