spirv-reader: remove multisampled_2d_array support

The only multisampled texture type supported by WGSL is
texture_multisampled_2d

Fixed: tint:780
Change-Id: Ie968311c802f858d087a411cda8f336627ad657b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/50446
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: Alan Baker <alanbaker@google.com>
diff --git a/src/reader/spirv/enum_converter.cc b/src/reader/spirv/enum_converter.cc
index cfcad1b..82421cc 100644
--- a/src/reader/spirv/enum_converter.cc
+++ b/src/reader/spirv/enum_converter.cc
@@ -110,7 +110,7 @@
       default:
         break;
     }
-    Fail() << "arrayed dimension must be 1D, 2D, or Cube. Got " << int(dim);
+    Fail() << "arrayed dimension must be 2D or Cube. Got " << int(dim);
     return ast::TextureDimension::kNone;
   }
   // Assume non-arrayed
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 582638e..c0f6a50 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -2046,6 +2046,21 @@
       return nullptr;
     }
 
+    if (image_type->is_arrayed()) {
+      // Give a nicer error message here, where we have the offending variable
+      // in hand, rather than inside the enum converter.
+      switch (image_type->dim()) {
+        case SpvDim2D:
+        case SpvDimCube:
+          break;
+        default:
+          Fail() << "WGSL arrayed textures must be 2d_array or cube_array: "
+                    "invalid multisampled texture variable "
+                 << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
+          return nullptr;
+      }
+    }
+
     const ast::TextureDimension dim =
         enum_converter_.ToDim(image_type->dim(), image_type->is_arrayed());
     if (dim == ast::TextureDimension::kNone) {
@@ -2067,6 +2082,11 @@
       if (image_type->depth() || usage.IsDepthTexture()) {
         ast_store_type = ty_.DepthTexture(dim);
       } else if (image_type->is_multisampled()) {
+        if (dim != ast::TextureDimension::k2d) {
+          Fail() << "WGSL multisampled textures must be 2d and non-arrayed: "
+                    "invalid multisampled texture variable "
+                 << namer_.Name(var.result_id()) << ": " << var.PrettyPrint();
+        }
         // Multisampled textures are never depth textures.
         ast_store_type =
             ty_.MultisampledTexture(dim, ast_sampled_component_type);
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index 1a2c2ef..1901c23 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -1261,9 +1261,10 @@
 
 TEST_P(SpvParserHandleTest_SampledImageAccessTest, Variable) {
   // Only declare the sampled image type, and the associated variable
-  // if the requested image type is a sampled image type.
-  const bool sampled_image_type = GetParam().spirv_image_type_details.find(
-                                      "1 Unknown") != std::string::npos;
+  // if the requested image type is a sampled image type, and not a
+  // multisampled texture
+  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
+                                         "0 1 Unknown") != std::string::npos;
   const auto assembly =
       Preamble() + R"(
      OpEntryPoint Fragment %main "main"
@@ -1299,7 +1300,7 @@
      %im_ty = OpTypeImage )" +
       GetParam().spirv_image_type_details + R"(
      %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-)" + (sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
+)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
       R"(
 
      %10 = OpVariable %ptr_sampler UniformConstant
@@ -1336,8 +1337,9 @@
      %sam = OpLoad %sampler %10
      %im = OpLoad %im_ty %20
 )" +
-      (sampled_image_type ? " %sampled_image = OpSampledImage %si_ty %im %sam "
-                          : "") +
+      (is_sampled_image_type
+           ? " %sampled_image = OpSampledImage %si_ty %im %sam\n"
+           : "") +
       GetParam().spirv_image_access +
       R"(
 
@@ -3341,43 +3343,9 @@
           }
         }
       }
-    })"},
-        // ImageFetch arrayed
-        {"%float 2D 0 1 1 1 Unknown",
-         "%99 = OpImageFetch %v4float %im %vi123 Sample %i1",
-         R"(Variable{
-    Decorations{
-      GroupDecoration{2}
-      BindingDecoration{1}
-    }
-    x_20
-    uniform_constant
-    __multisampled_texture_2d_array__f32
-  })",
-         R"(VariableDeclStatement{
-      VariableConst{
-        x_99
-        none
-        __vec_4__f32
-        {
-          Call[not set]{
-            Identifier[not set]{textureLoad}
-            (
-              Identifier[not set]{x_20}
-              MemberAccessor[not set]{
-                Identifier[not set]{vi123}
-                Identifier[not set]{xy}
-              }
-              MemberAccessor[not set]{
-                Identifier[not set]{vi123}
-                Identifier[not set]{z}
-              }
-              Identifier[not set]{i1}
-            )
-          }
-        }
-      }
-    })"}}));
+    })"}}  // ImageFetch arrayed
+                        // Not in WebGPU
+                        ));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageFetch_Multisampled_ConvertSampleOperand,
@@ -3848,46 +3816,12 @@
           }
         }
       }
-    })"},
+    })"}
         // 3D array storage image doesn't exist.
 
         // Multisampled array
-        {"%float 2D 0 1 1 1 Unknown",
-         "%99 = OpImageQuerySize %v3int %im \n"
-         "%98 = OpImageRead %v4float %im %vi123\n",
-         R"(Variable{
-    Decorations{
-      GroupDecoration{2}
-      BindingDecoration{1}
-    }
-    x_20
-    uniform_constant
-    __multisampled_texture_2d_array__f32
-  })",
-         R"(VariableDeclStatement{
-      VariableConst{
-        x_99
-        none
-        __vec_3__i32
-        {
-          TypeConstructor[not set]{
-            __vec_3__i32
-            Call[not set]{
-              Identifier[not set]{textureDimensions}
-              (
-                Identifier[not set]{x_20}
-              )
-            }
-            Call[not set]{
-              Identifier[not set]{textureNumLayers}
-              (
-                Identifier[not set]{x_20}
-              )
-            }
-          }
-        }
-      }
-    })"}}));
+        // Not in WebGPU
+    }));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageQuerySizeLod_NonArrayed_SignedResult_SignedLevel,
@@ -4622,13 +4556,13 @@
       }
     })"}}));
 
-INSTANTIATE_TEST_SUITE_P(
-    ImageQuerySamples_SignedResult,
-    SpvParserHandleTest_SampledImageAccessTest,
-    ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // Multsample 2D
-        {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySamples %int %im\n",
-         R"(Variable{
+INSTANTIATE_TEST_SUITE_P(ImageQuerySamples_SignedResult,
+                         SpvParserHandleTest_SampledImageAccessTest,
+                         ::testing::ValuesIn(std::vector<ImageAccessCase>{
+                             // Multsample 2D
+                             {"%float 2D 0 0 1 1 Unknown",
+                              "%99 = OpImageQuerySamples %int %im\n",
+                              R"(Variable{
     Decorations{
       GroupDecoration{2}
       BindingDecoration{1}
@@ -4637,7 +4571,7 @@
     uniform_constant
     __multisampled_texture_2d__f32
   })",
-         R"(VariableDeclStatement{
+                              R"(VariableDeclStatement{
       VariableConst{
         x_99
         none
@@ -4651,34 +4585,11 @@
           }
         }
       }
-    })"},
+    })"}
 
-        // Multisample 2D array
-        {"%float 2D 0 1 1 1 Unknown", "%99 = OpImageQuerySamples %int %im\n",
-         R"(Variable{
-    Decorations{
-      GroupDecoration{2}
-      BindingDecoration{1}
-    }
-    x_20
-    uniform_constant
-    __multisampled_texture_2d_array__f32
-  })",
-         R"(VariableDeclStatement{
-      VariableConst{
-        x_99
-        none
-        __i32
-        {
-          Call[not set]{
-            Identifier[not set]{textureNumSamples}
-            (
-              Identifier[not set]{x_20}
-            )
-          }
-        }
-      }
-    })"}}));
+                             // Multisample 2D array
+                             // Not in WebGPU
+                         }));
 
 INSTANTIATE_TEST_SUITE_P(
     // Translation must inject a type coersion from signed to unsigned.
@@ -4713,66 +4624,36 @@
           }
         }
       }
-    })"},
+    })"}
 
         // Multisample 2D array
-        {"%float 2D 0 1 1 1 Unknown", "%99 = OpImageQuerySamples %uint %im\n",
-         R"(Variable{
-    Decorations{
-      GroupDecoration{2}
-      BindingDecoration{1}
-    }
-    x_20
-    uniform_constant
-    __multisampled_texture_2d_array__f32
-  })",
-         R"(VariableDeclStatement{
-      VariableConst{
-        x_99
-        none
-        __u32
-        {
-          TypeConstructor[not set]{
-            __u32
-            Call[not set]{
-              Identifier[not set]{textureNumSamples}
-              (
-                Identifier[not set]{x_20}
-              )
-            }
-          }
-        }
-      }
-    })"}}));
+        // Not in WebGPU
+    }));
 
-struct ImageCoordsCase {
+struct ImageDeclCase {
   // SPIR-V image type, excluding result ID and opcode
   std::string spirv_image_type_details;
-  std::string spirv_image_access;
+  std::string spirv_image_access;  // Optional instruction to provoke use
   std::string expected_error;
-  std::vector<std::string> expected_expressions;
+  std::string expected_decl;
 };
 
-inline std::ostream& operator<<(std::ostream& out, const ImageCoordsCase& c) {
-  out << "ImageAccessCase(" << c.spirv_image_type_details << "\n"
-      << c.spirv_image_access << "\n";
-
-  for (auto e : c.expected_expressions) {
-    out << e << ",";
-  }
-  out << ")" << std::endl;
+inline std::ostream& operator<<(std::ostream& out, const ImageDeclCase& c) {
+  out << "ImageDeclCase(" << c.spirv_image_type_details << "\n"
+      << "access: " << c.spirv_image_access << "\n"
+      << "error: " << c.expected_error << "\n"
+      << "decl:" << c.expected_decl << "\n)";
   return out;
 }
 
-using SpvParserHandleTest_ImageCoordsTest =
-    SpvParserTestBase<::testing::TestWithParam<ImageCoordsCase>>;
+using SpvParserHandleTest_ImageDeclTest =
+    SpvParserTestBase<::testing::TestWithParam<ImageDeclCase>>;
 
-TEST_P(SpvParserHandleTest_ImageCoordsTest,
-       MakeCoordinateOperandsForImageAccess) {
+TEST_P(SpvParserHandleTest_ImageDeclTest, DeclareHandle) {
   // Only declare the sampled image type, and the associated variable
-  // if the requested image type is a sampled image type.
-  const bool sampled_image_type = GetParam().spirv_image_type_details.find(
-                                      "1 Unknown") != std::string::npos;
+  // if the requested image type is a sampled image type and not multisampled.
+  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
+                                         "0 1 Unknown") != std::string::npos;
   const auto assembly =
       Preamble() + R"(
      OpEntryPoint Fragment %100 "main"
@@ -4804,7 +4685,7 @@
      %im_ty = OpTypeImage )" +
       GetParam().spirv_image_type_details + R"(
      %ptr_im_ty = OpTypePointer UniformConstant %im_ty
-)" + (sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
+)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
       R"(
 
      %ptr_float = OpTypePointer Function %float
@@ -4837,8 +4718,158 @@
      %im = OpLoad %im_ty %20
 
 )" +
-      (sampled_image_type ? " %sampled_image = OpSampledImage %si_ty %im %sam "
-                          : "") +
+      (is_sampled_image_type
+           ? " %sampled_image = OpSampledImage %si_ty %im %sam "
+           : "") +
+      GetParam().spirv_image_access +
+      R"(
+     ; Use an anchor for the cases when the image access doesn't have a result ID.
+     %1000 = OpCopyObject %uint %uint_0
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto p = parser(test::Assemble(assembly));
+  const bool succeeded = p->BuildAndParseInternalModule();
+  if (succeeded) {
+    EXPECT_TRUE(GetParam().expected_error.empty());
+    const auto got = p->program().to_str();
+    EXPECT_THAT(got, HasSubstr(GetParam().expected_decl));
+  } else {
+    EXPECT_FALSE(GetParam().expected_error.empty());
+    EXPECT_THAT(p->error(), HasSubstr(GetParam().expected_error));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    Multisampled_Only2DNonArrayedIsValid,
+    SpvParserHandleTest_ImageDeclTest,
+    ::testing::ValuesIn(std::vector<ImageDeclCase>{
+        {"%float 1D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float 1D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL arrayed textures must be 2d_array or cube_array: ", ""},
+        {"%float 2D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "", R"(
+  Variable{
+    Decorations{
+      GroupDecoration{2}
+      BindingDecoration{1}
+    }
+    x_20
+    uniform_constant
+    __multisampled_texture_2d__f32
+  }
+)"},
+        {"%float 2D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float 3D 0 0 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float 3D 0 1 1 1 Unknown", "%result = OpImageQuerySamples %uint %im",
+         "WGSL arrayed textures must be 2d_array or cube_array: ", ""},
+        {"%float Cube 0 0 1 1 Unknown",
+         "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""},
+        {"%float Cube 0 1 1 1 Unknown",
+         "%result = OpImageQuerySamples %uint %im",
+         "WGSL multisampled textures must be 2d and non-arrayed: ", ""}}));
+
+struct ImageCoordsCase {
+  // SPIR-V image type, excluding result ID and opcode
+  std::string spirv_image_type_details;
+  std::string spirv_image_access;
+  std::string expected_error;
+  std::vector<std::string> expected_expressions;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const ImageCoordsCase& c) {
+  out << "ImageCoordsCase(" << c.spirv_image_type_details << "\n"
+      << c.spirv_image_access << "\n"
+      << "expected_error(" << c.expected_error << ")\n";
+
+  for (auto e : c.expected_expressions) {
+    out << e << ",";
+  }
+  out << ")" << std::endl;
+  return out;
+}
+
+using SpvParserHandleTest_ImageCoordsTest =
+    SpvParserTestBase<::testing::TestWithParam<ImageCoordsCase>>;
+
+TEST_P(SpvParserHandleTest_ImageCoordsTest,
+       MakeCoordinateOperandsForImageAccess) {
+  // Only declare the sampled image type, and the associated variable
+  // if the requested image type is a sampled image type and not multisampled.
+  const bool is_sampled_image_type = GetParam().spirv_image_type_details.find(
+                                         "0 1 Unknown") != std::string::npos;
+  const auto assembly =
+      Preamble() + R"(
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+     OpName %float_var "float_var"
+     OpName %ptr_float "ptr_float"
+     OpName %i1 "i1"
+     OpName %vi12 "vi12"
+     OpName %vi123 "vi123"
+     OpName %vi1234 "vi1234"
+     OpName %u1 "u1"
+     OpName %vu12 "vu12"
+     OpName %vu123 "vu123"
+     OpName %vu1234 "vu1234"
+     OpName %f1 "f1"
+     OpName %vf12 "vf12"
+     OpName %vf123 "vf123"
+     OpName %vf1234 "vf1234"
+     OpDecorate %10 DescriptorSet 0
+     OpDecorate %10 Binding 0
+     OpDecorate %20 DescriptorSet 2
+     OpDecorate %20 Binding 1
+     OpDecorate %30 DescriptorSet 0
+     OpDecorate %30 Binding 1
+)" + CommonBasicTypes() +
+      R"(
+     %sampler = OpTypeSampler
+     %ptr_sampler = OpTypePointer UniformConstant %sampler
+     %im_ty = OpTypeImage )" +
+      GetParam().spirv_image_type_details + R"(
+     %ptr_im_ty = OpTypePointer UniformConstant %im_ty
+)" + (is_sampled_image_type ? " %si_ty = OpTypeSampledImage %im_ty " : "") +
+      R"(
+
+     %ptr_float = OpTypePointer Function %float
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_im_ty UniformConstant
+     %30 = OpVariable %ptr_sampler UniformConstant ; comparison sampler, when needed
+
+     %100 = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %float_var = OpVariable %ptr_float Function
+
+     %i1 = OpCopyObject %int %int_1
+     %vi12 = OpCopyObject %v2int %the_vi12
+     %vi123 = OpCopyObject %v3int %the_vi123
+     %vi1234 = OpCopyObject %v4int %the_vi1234
+
+     %u1 = OpCopyObject %uint %uint_1
+     %vu12 = OpCopyObject %v2uint %the_vu12
+     %vu123 = OpCopyObject %v3uint %the_vu123
+     %vu1234 = OpCopyObject %v4uint %the_vu1234
+
+     %f1 = OpCopyObject %float %float_1
+     %vf12 = OpCopyObject %v2float %the_vf12
+     %vf123 = OpCopyObject %v3float %the_vf123
+     %vf1234 = OpCopyObject %v4float %the_vf1234
+
+     %sam = OpLoad %sampler %10
+     %im = OpLoad %im_ty %20
+
+)" +
+      (is_sampled_image_type
+           ? " %sampled_image = OpSampledImage %si_ty %im %sam "
+           : "") +
       GetParam().spirv_image_access +
       R"(
      ; Use an anchor for the cases when the image access doesn't have a result ID.
@@ -4876,7 +4907,7 @@
                   ::testing::ContainerEq(GetParam().expected_expressions));
     } else {
       EXPECT_FALSE(fe.success());
-      EXPECT_THAT(p->error(), Eq(GetParam().expected_error));
+      EXPECT_THAT(p->error(), Eq(GetParam().expected_error)) << assembly;
       EXPECT_TRUE(result.empty());
     }
   }