Validate binding array in IR.

Update the IR validator to validate the `binding_array` requirements.

Fixed: 441538564
Change-Id: I5656f35108c97392bef6fdc3c6132d7fc7b97167
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/260314
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 105c3f3..1f0e65b 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -81,6 +81,7 @@
 #include "src/tint/lang/core/ir/user_call.h"
 #include "src/tint/lang/core/ir/var.h"
 #include "src/tint/lang/core/type/array.h"
+#include "src/tint/lang/core/type/binding_array.h"
 #include "src/tint/lang/core/type/bool.h"
 #include "src/tint/lang/core/type/f16.h"
 #include "src/tint/lang/core/type/f32.h"
@@ -2073,6 +2074,20 @@
                 }
                 return true;
             },
+            [&](const core::type::BindingArray* t) {
+                if (!t->Count()->Is<core::type::ConstantArrayCount>()) {
+                    diag() << "binding_array count must be a constant expression";
+                    return false;
+                }
+
+                if (!capabilities_.Contains(Capability::kAllowNonCoreTypes)) {
+                    if (!t->ElemType()->Is<core::type::SampledTexture>()) {
+                        diag() << "binding_array element type must be a sampled texture type";
+                        return false;
+                    }
+                }
+                return true;
+            },
             [](Default) { return true; });
     };
 
diff --git a/src/tint/lang/core/ir/validator_type_test.cc b/src/tint/lang/core/ir/validator_type_test.cc
index 17aa8f2..a072eaa 100644
--- a/src/tint/lang/core/ir/validator_type_test.cc
+++ b/src/tint/lang/core/ir/validator_type_test.cc
@@ -39,6 +39,7 @@
 #include "src/tint/lang/core/number.h"
 #include "src/tint/lang/core/type/abstract_float.h"
 #include "src/tint/lang/core/type/abstract_int.h"
+#include "src/tint/lang/core/type/binding_array.h"
 #include "src/tint/lang/core/type/clone_context.h"
 #include "src/tint/lang/core/type/manager.h"
 #include "src/tint/lang/core/type/matrix.h"
@@ -473,6 +474,51 @@
                                          std::make_tuple(false, TypeBuilder<core::type::Bool>),
                                          std::make_tuple(false, TypeBuilder<core::type::Void>)));
 
+TEST_F(IR_ValidatorTest, BindingArray) {
+    b.Append(mod.root_block, [&] {
+        auto* var = b.Var(
+            "m", AddressSpace::kHandle,
+            ty.binding_array(ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), 4));
+        var->SetBindingPoint(0, 0);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, BindingArrayRuntimeCount) {
+    b.Append(mod.root_block, [&] {
+        auto* var = b.Var("m", AddressSpace::kHandle,
+                          ty.Get<core::type::BindingArray>(
+                              ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()),
+                              ty.Get<core::type::RuntimeArrayCount>()));
+        var->SetBindingPoint(0, 0);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(
+        res.Failure().reason,
+        testing::HasSubstr(R"(:2:3 error: var: binding_array count must be a constant expression
+  %m:ptr<handle, binding_array<texture_2d<f32>, >, read> = var undef @binding_point(0, 0)
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^)"));
+}
+
+TEST_F(IR_ValidatorTest, BindingArrayNonSampledTexture) {
+    b.Append(mod.root_block, [&] {
+        auto* var = b.Var("m", AddressSpace::kHandle, ty.binding_array(ty.external_texture(), 5));
+        var->SetBindingPoint(0, 0);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_THAT(res.Failure().reason,
+                testing::HasSubstr(
+                    R"(:2:3 error: var: binding_array element type must be a sampled texture type
+  %m:ptr<handle, binding_array<texture_external, 5>, read> = var undef @binding_point(0, 0)
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^)"));
+}
+
 using Type_MultisampledTextureTypeAndDimension =
     IRTestParamHelper<std::tuple<std::tuple<
                                      /* type_allowed */ bool,
diff --git a/src/tint/lang/msl/writer/raise/argument_buffers_test.cc b/src/tint/lang/msl/writer/raise/argument_buffers_test.cc
index e6e17b4..b56c6ee 100644
--- a/src/tint/lang/msl/writer/raise/argument_buffers_test.cc
+++ b/src/tint/lang/msl/writer/raise/argument_buffers_test.cc
@@ -420,7 +420,7 @@
 TEST_F(MslWriter_ArgumentBuffersTest, HandleTypes_BindingArray) {
     auto* texture_type = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
     auto* var_t = b.Var("t", ty.ptr<handle>(ty.binding_array(texture_type, 3u)));
-    auto* var_s = b.Var("s", ty.ptr<handle>(ty.binding_array(ty.sampler(), 3u)));
+    auto* var_s = b.Var("s", ty.ptr<handle>(ty.sampler()));
     var_t->SetBindingPoint(1, 2);
     var_s->SetBindingPoint(3, 4);
     mod.root_block->Append(var_t);
@@ -429,7 +429,7 @@
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
     b.Append(func->Block(), [&] {
         auto* load_t = b.Load(b.Access(ty.ptr<handle>(texture_type), var_t, 0_i));
-        auto* load_s = b.Load(b.Access(ty.ptr<handle>(ty.sampler()), var_s, 0_i));
+        auto* load_s = b.Load(var_s);
         b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s, b.Splat<vec2<f32>>(0_f));
         b.Return(func);
     });
@@ -437,16 +437,15 @@
     auto* src = R"(
 $B1: {  # root
   %t:ptr<handle, binding_array<texture_2d<f32>, 3>, read> = var undef @binding_point(1, 2)
-  %s:ptr<handle, binding_array<sampler, 3>, read> = var undef @binding_point(3, 4)
+  %s:ptr<handle, sampler, read> = var undef @binding_point(3, 4)
 }
 
 %foo = @fragment func():void {
   $B2: {
     %4:ptr<handle, texture_2d<f32>, read> = access %t, 0i
     %5:texture_2d<f32> = load %4
-    %6:ptr<handle, sampler, read> = access %s, 0i
-    %7:sampler = load %6
-    %8:vec4<f32> = textureSample %5, %7, vec2<f32>(0.0f)
+    %6:sampler = load %s
+    %7:vec4<f32> = textureSample %5, %6, vec2<f32>(0.0f)
     ret
   }
 }
@@ -459,7 +458,7 @@
 }
 
 tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
-  s:binding_array<sampler, 3> @offset(0), @binding_point(3, 4)
+  s:sampler @offset(0), @binding_point(3, 4)
 }
 
 %foo = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(0, 20)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(0, 30)]):void {
@@ -468,9 +467,8 @@
     %5:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
     %6:binding_array<texture_2d<f32>, 3> = access %5, 0u
     %7:texture_2d<f32> = access %6, 0i
-    %8:binding_array<sampler, 3> = access %4, 0u
-    %9:sampler = access %8, 0i
-    %10:vec4<f32> = textureSample %7, %9, vec2<f32>(0.0f)
+    %8:sampler = access %4, 0u
+    %9:vec4<f32> = textureSample %7, %8, vec2<f32>(0.0f)
     ret
   }
 }
@@ -937,7 +935,7 @@
 TEST_F(MslWriter_ArgumentBuffersTest, CallFunctionThatUsesVars_HandleTypes_BindingArray) {
     auto* texture_type = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
     auto* var_t = b.Var("t", ty.ptr<handle>(ty.binding_array(texture_type, 3u)));
-    auto* var_s = b.Var("s", ty.ptr<handle>(ty.binding_array(ty.sampler(), 3u)));
+    auto* var_s = b.Var("s", ty.ptr<handle>(ty.sampler()));
     var_t->SetBindingPoint(1, 2);
     var_s->SetBindingPoint(3, 4);
     mod.root_block->Append(var_t);
@@ -948,7 +946,7 @@
     foo->SetParams({param});
     b.Append(foo->Block(), [&] {
         auto* load_t = b.Load(b.Access(ty.ptr<handle>(texture_type), var_t, 0_i));
-        auto* load_s = b.Load(b.Access(ty.ptr<handle>(ty.sampler()), var_s, 0_i));
+        auto* load_s = b.Load(var_s);
         auto* result = b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s,
                                          b.Splat<vec2<f32>>(0_f));
         b.Return(foo, result);
@@ -963,22 +961,21 @@
     auto* src = R"(
 $B1: {  # root
   %t:ptr<handle, binding_array<texture_2d<f32>, 3>, read> = var undef @binding_point(1, 2)
-  %s:ptr<handle, binding_array<sampler, 3>, read> = var undef @binding_point(3, 4)
+  %s:ptr<handle, sampler, read> = var undef @binding_point(3, 4)
 }
 
 %foo = func(%param:i32):vec4<f32> {
   $B2: {
     %5:ptr<handle, texture_2d<f32>, read> = access %t, 0i
     %6:texture_2d<f32> = load %5
-    %7:ptr<handle, sampler, read> = access %s, 0i
-    %8:sampler = load %7
-    %9:vec4<f32> = textureSample %6, %8, vec2<f32>(0.0f)
-    ret %9
+    %7:sampler = load %s
+    %8:vec4<f32> = textureSample %6, %7, vec2<f32>(0.0f)
+    ret %8
   }
 }
 %main = @fragment func():void {
   $B3: {
-    %11:vec4<f32> = call %foo, 42i
+    %10:vec4<f32> = call %foo, 42i
     ret
   }
 }
@@ -991,24 +988,23 @@
 }
 
 tint_arg_buffer_struct_3 = struct @align(1), @core.explicit_layout {
-  s:binding_array<sampler, 3> @offset(0), @binding_point(3, 4)
+  s:sampler @offset(0), @binding_point(3, 4)
 }
 
-%foo = func(%param:i32, %t:binding_array<texture_2d<f32>, 3>, %s:binding_array<sampler, 3>):vec4<f32> {
+%foo = func(%param:i32, %t:binding_array<texture_2d<f32>, 3>, %s:sampler):vec4<f32> {
   $B1: {
     %5:texture_2d<f32> = access %t, 0i
-    %6:sampler = access %s, 0i
-    %7:vec4<f32> = textureSample %5, %6, vec2<f32>(0.0f)
-    ret %7
+    %6:vec4<f32> = textureSample %5, %s, vec2<f32>(0.0f)
+    ret %6
   }
 }
 %main = @fragment func(%tint_arg_buffer_1:ptr<uniform, tint_arg_buffer_struct_1, read> [@binding_point(0, 20)], %tint_arg_buffer_3:ptr<uniform, tint_arg_buffer_struct_3, read> [@binding_point(0, 30)]):void {
   $B2: {
-    %11:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
-    %12:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
-    %13:binding_array<texture_2d<f32>, 3> = access %12, 0u
-    %14:binding_array<sampler, 3> = access %11, 0u
-    %15:vec4<f32> = call %foo, 42i, %13, %14
+    %10:tint_arg_buffer_struct_3 = load %tint_arg_buffer_3
+    %11:tint_arg_buffer_struct_1 = load %tint_arg_buffer_1
+    %12:binding_array<texture_2d<f32>, 3> = access %11, 0u
+    %13:sampler = access %10, 0u
+    %14:vec4<f32> = call %foo, 42i, %12, %13
     ret
   }
 }
diff --git a/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc b/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc
index f0215bd1..ff2391c 100644
--- a/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc
+++ b/src/tint/lang/msl/writer/raise/module_scope_vars_test.cc
@@ -377,7 +377,7 @@
 TEST_F(MslWriter_ModuleScopeVarsTest, HandleTypes_BindingArray) {
     auto* texture_type = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
     auto* var_t = b.Var("t", ty.ptr<handle>(ty.binding_array(texture_type, 3u)));
-    auto* var_s = b.Var("s", ty.ptr<handle>(ty.binding_array(ty.sampler(), 3u)));
+    auto* var_s = b.Var("s", ty.ptr<handle>(ty.sampler()));
     var_t->SetBindingPoint(1, 2);
     var_s->SetBindingPoint(3, 4);
     mod.root_block->Append(var_t);
@@ -386,7 +386,7 @@
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
     b.Append(func->Block(), [&] {
         auto* load_t = b.Load(b.Access(ty.ptr<handle>(texture_type), var_t, 0_i));
-        auto* load_s = b.Load(b.Access(ty.ptr<handle>(ty.sampler()), var_s, 0_i));
+        auto* load_s = b.Load(var_s);
         b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s, b.Splat<vec2<f32>>(0_f));
         b.Return(func);
     });
@@ -394,16 +394,15 @@
     auto* src = R"(
 $B1: {  # root
   %t:ptr<handle, binding_array<texture_2d<f32>, 3>, read> = var undef @binding_point(1, 2)
-  %s:ptr<handle, binding_array<sampler, 3>, read> = var undef @binding_point(3, 4)
+  %s:ptr<handle, sampler, read> = var undef @binding_point(3, 4)
 }
 
 %foo = @fragment func():void {
   $B2: {
     %4:ptr<handle, texture_2d<f32>, read> = access %t, 0i
     %5:texture_2d<f32> = load %4
-    %6:ptr<handle, sampler, read> = access %s, 0i
-    %7:sampler = load %6
-    %8:vec4<f32> = textureSample %5, %7, vec2<f32>(0.0f)
+    %6:sampler = load %s
+    %7:vec4<f32> = textureSample %5, %6, vec2<f32>(0.0f)
     ret
   }
 }
@@ -413,18 +412,17 @@
     auto* expect = R"(
 tint_module_vars_struct = struct @align(1) {
   t:binding_array<texture_2d<f32>, 3> @offset(0)
-  s:binding_array<sampler, 3> @offset(0)
+  s:sampler @offset(0)
 }
 
-%foo = @fragment func(%t:binding_array<texture_2d<f32>, 3> [@binding_point(1, 2)], %s:binding_array<sampler, 3> [@binding_point(3, 4)]):void {
+%foo = @fragment func(%t:binding_array<texture_2d<f32>, 3> [@binding_point(1, 2)], %s:sampler [@binding_point(3, 4)]):void {
   $B1: {
     %4:tint_module_vars_struct = construct %t, %s
     %tint_module_vars:tint_module_vars_struct = let %4
     %6:binding_array<texture_2d<f32>, 3> = access %tint_module_vars, 0u
     %7:texture_2d<f32> = access %6, 0i
-    %8:binding_array<sampler, 3> = access %tint_module_vars, 1u
-    %9:sampler = access %8, 0i
-    %10:vec4<f32> = textureSample %7, %9, vec2<f32>(0.0f)
+    %8:sampler = access %tint_module_vars, 1u
+    %9:vec4<f32> = textureSample %7, %8, vec2<f32>(0.0f)
     ret
   }
 }
@@ -882,7 +880,7 @@
 TEST_F(MslWriter_ModuleScopeVarsTest, CallFunctionThatUsesVars_HandleTypes_BindingArray) {
     auto* texture_type = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
     auto* var_t = b.Var("t", ty.ptr<handle>(ty.binding_array(texture_type, 3u)));
-    auto* var_s = b.Var("s", ty.ptr<handle>(ty.binding_array(ty.sampler(), 3u)));
+    auto* var_s = b.Var("s", ty.ptr<handle>(ty.sampler()));
     var_t->SetBindingPoint(1, 2);
     var_s->SetBindingPoint(3, 4);
     mod.root_block->Append(var_t);
@@ -893,7 +891,7 @@
     foo->SetParams({param});
     b.Append(foo->Block(), [&] {
         auto* load_t = b.Load(b.Access(ty.ptr<handle>(texture_type), var_t, 0_i));
-        auto* load_s = b.Load(b.Access(ty.ptr<handle>(ty.sampler()), var_s, 0_i));
+        auto* load_s = b.Load(var_s);
         auto* result = b.Call<vec4<f32>>(core::BuiltinFn::kTextureSample, load_t, load_s,
                                          b.Splat<vec2<f32>>(0_f));
         b.Return(foo, result);
@@ -908,22 +906,21 @@
     auto* src = R"(
 $B1: {  # root
   %t:ptr<handle, binding_array<texture_2d<f32>, 3>, read> = var undef @binding_point(1, 2)
-  %s:ptr<handle, binding_array<sampler, 3>, read> = var undef @binding_point(3, 4)
+  %s:ptr<handle, sampler, read> = var undef @binding_point(3, 4)
 }
 
 %foo = func(%param:i32):vec4<f32> {
   $B2: {
     %5:ptr<handle, texture_2d<f32>, read> = access %t, 0i
     %6:texture_2d<f32> = load %5
-    %7:ptr<handle, sampler, read> = access %s, 0i
-    %8:sampler = load %7
-    %9:vec4<f32> = textureSample %6, %8, vec2<f32>(0.0f)
-    ret %9
+    %7:sampler = load %s
+    %8:vec4<f32> = textureSample %6, %7, vec2<f32>(0.0f)
+    ret %8
   }
 }
 %main = @fragment func():void {
   $B3: {
-    %11:vec4<f32> = call %foo, 42i
+    %10:vec4<f32> = call %foo, 42i
     ret
   }
 }
@@ -933,24 +930,23 @@
     auto* expect = R"(
 tint_module_vars_struct = struct @align(1) {
   t:binding_array<texture_2d<f32>, 3> @offset(0)
-  s:binding_array<sampler, 3> @offset(0)
+  s:sampler @offset(0)
 }
 
 %foo = func(%param:i32, %tint_module_vars:tint_module_vars_struct):vec4<f32> {
   $B1: {
     %4:binding_array<texture_2d<f32>, 3> = access %tint_module_vars, 0u
     %5:texture_2d<f32> = access %4, 0i
-    %6:binding_array<sampler, 3> = access %tint_module_vars, 1u
-    %7:sampler = access %6, 0i
-    %8:vec4<f32> = textureSample %5, %7, vec2<f32>(0.0f)
-    ret %8
+    %6:sampler = access %tint_module_vars, 1u
+    %7:vec4<f32> = textureSample %5, %6, vec2<f32>(0.0f)
+    ret %7
   }
 }
-%main = @fragment func(%t:binding_array<texture_2d<f32>, 3> [@binding_point(1, 2)], %s:binding_array<sampler, 3> [@binding_point(3, 4)]):void {
+%main = @fragment func(%t:binding_array<texture_2d<f32>, 3> [@binding_point(1, 2)], %s:sampler [@binding_point(3, 4)]):void {
   $B2: {
-    %12:tint_module_vars_struct = construct %t, %s
-    %tint_module_vars_1:tint_module_vars_struct = let %12  # %tint_module_vars_1: 'tint_module_vars'
-    %14:vec4<f32> = call %foo, 42i, %tint_module_vars_1
+    %11:tint_module_vars_struct = construct %t, %s
+    %tint_module_vars_1:tint_module_vars_struct = let %11  # %tint_module_vars_1: 'tint_module_vars'
+    %13:vec4<f32> = call %foo, 42i, %tint_module_vars_1
     ret
   }
 }