spirv-reader: support OpImageGather

Bug: tint:1336
Change-Id: I771b09e7568f1f022a316f2ba0bc72c03f735aa1
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/74800
Auto-Submit: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index f086901..a51cc24 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -494,8 +494,9 @@
 }
 
 // @param opcode a SPIR-V opcode
-// @returns true if the given instruction is an image sampling operation.
-bool IsImageSampling(SpvOp opcode) {
+// @returns true if the given instruction is an image sampling, gather,
+// or gather-compare operation.
+bool IsImageSamplingOrGatherOrDrefGather(SpvOp opcode) {
   switch (opcode) {
     case SpvOpImageSampleImplicitLod:
     case SpvOpImageSampleExplicitLod:
@@ -506,6 +507,8 @@
     case SpvOpImageSampleProjExplicitLod:
     case SpvOpImageSampleProjDrefImplicitLod:
     case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageGather:
+    case SpvOpImageDrefGather:
       return true;
     default:
       break;
@@ -5247,6 +5250,7 @@
   }
   params.push_back(GetImageExpression(inst));
 
+  // Form the sampler operand, if needed.
   if (IsSampledImageAccess(opcode)) {
     // Form the sampler operand.
     if (auto* sampler = GetSamplerExpression(inst)) {
@@ -5256,6 +5260,7 @@
     }
   }
 
+  // Find the texture type.
   const Pointer* texture_ptr_type = parser_impl_.GetTypeForHandleVar(*image);
   if (!texture_ptr_type) {
     return Fail();
@@ -5309,8 +5314,16 @@
       }
       break;
     case SpvOpImageGather:
+      builtin_name = "textureGather";
+      if (!texture_type->Is<DepthTexture>()) {
+        // The explicit component is the *first* argument in WGSL.
+        params.insert(params.begin(), ToI32(MakeOperand(inst, arg_index)).expr);
+      }
+      // Skip over the component operand, even for depth textures.
+      arg_index++;
+      break;
     case SpvOpImageDrefGather:
-      return Fail() << " image gather is not yet supported";
+      return Fail() << " image dref gather is not yet supported";
     case SpvOpImageFetch:
     case SpvOpImageRead:
       // Read a single texel from a sampled or storage image.
@@ -5407,8 +5420,9 @@
   }
   if (arg_index < num_args &&
       (image_operands_mask & SpvImageOperandsConstOffsetMask)) {
-    if (!IsImageSampling(opcode)) {
-      return Fail() << "ConstOffset is only permitted for sampling operations: "
+    if (!IsImageSamplingOrGatherOrDrefGather(opcode)) {
+      return Fail() << "ConstOffset is only permitted for sampling, gather, or "
+                       "depth-reference gather operations: "
                     << inst.PrettyPrint();
     }
     switch (texture_type->dims) {
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index 0215bc6..5d99c18 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -929,6 +929,7 @@
   EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage));
   EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
 
+  // TODO(dneto): remove this. crbug.com/tint/1336
   if (inst.find("Gather") != std::string::npos) {
     // WGSL does not support Gather instructions yet.
     // So don't emit them as part of a "passing" corpus.
@@ -986,6 +987,7 @@
   EXPECT_THAT(su.to_str(), Eq(GetParam().expected_sampler_usage));
   EXPECT_THAT(iu.to_str(), Eq(GetParam().expected_image_usage));
 
+  // TODO(dneto): remove this. crbug.com/tint/1336
   if (inst.find("Gather") != std::string::npos) {
     // WGSL does not support Gather instructions yet.
     // So don't emit them as part of a "passing" corpus.
@@ -1004,8 +1006,6 @@
     SpvParserHandleTest_RegisterHandleUsage_SampledImage,
     ::testing::Values(
 
-        // Test image gather even though WGSL doesn't support it yet.
-
         // OpImageGather
         UsageImageAccessCase{"%result = OpImageGather "
                              "%v4float %sampled_image %coords %uint_1",
@@ -1565,24 +1565,170 @@
        DISABLED_FunctionParam) {}
 
 INSTANTIATE_TEST_SUITE_P(
-    DISABLED_ImageGather,
+    ImageGather,
     SpvParserHandleTest_SampledImageAccessTest,
     ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // TODO(dneto): OpImageGather
-        // TODO(dneto): OpImageGather with ConstOffset (signed and unsigned)
-        // TODO(dneto): OpImageGather with Offset (signed and unsigned)
-        // TODO(dneto): OpImageGather with Offsets (signed and unsigned)
-    }));
+        // OpImageGather 2D
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_2d<f32>;)",
+                        "textureGather(1, x_20, x_10, coords12)"},
+        // OpImageGather 2D ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 0 0 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords12 %int_1 ConstOffset %offsets2d",
+            R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_2d<f32>;)",
+            "textureGather(1, x_20, x_10, coords12, vec2<i32>(3, 4))"},
+        // OpImageGather 2D ConstOffset unsigned
+        ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_2d<f32>;)",
+                        "textureGather(1, x_20, x_10, coords12, "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather 2D Array
+        ImageAccessCase{"%float 2D 0 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_2d_array<f32>;)",
+                        "textureGather(1, x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)))"},
+        // OpImageGather 2D Array ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 0 1 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords123 %int_1 ConstOffset %offsets2d",
+            R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_2d_array<f32>;)",
+            "textureGather(1, x_20, x_10, coords123.xy, "
+            "i32(round(coords123.z)), vec2<i32>(3, 4))"},
+        // OpImageGather 2D Array ConstOffset unsigned
+        ImageAccessCase{"%float 2D 0 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_2d_array<f32>;)",
+                        "textureGather(1, x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)), "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather Cube
+        ImageAccessCase{"%float Cube 0 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_cube<f32>;)",
+                        "textureGather(1, x_20, x_10, coords123)"},
+        // OpImageGather Cube Array
+        ImageAccessCase{"%float Cube 0 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords1234 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_cube_array<f32>;)",
+                        "textureGather(1, x_20, x_10, coords1234.xyz, "
+                        "i32(round(coords1234.w)))"},
+        // OpImageGather 2DDepth
+        ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_2d;)",
+                        "textureGather(x_20, x_10, coords12)"},
+        // OpImageGather 2DDepth ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 1 0 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords12 %int_1 ConstOffset %offsets2d",
+            R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_2d;)",
+            "textureGather(x_20, x_10, coords12, vec2<i32>(3, 4))"},
+        // OpImageGather 2DDepth ConstOffset unsigned
+        ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords12 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_2d;)",
+                        "textureGather(x_20, x_10, coords12, "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather 2DDepth Array
+        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_2d_array;)",
+                        "textureGather(x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)))"},
+        // OpImageGather 2DDepth Array ConstOffset signed
+        ImageAccessCase{
+            "%float 2D 1 1 0 1 Unknown",
+            "%result = OpImageGather "
+            "%v4float %sampled_image %coords123 %int_1 ConstOffset %offsets2d",
+            R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_2d_array;)",
+            "textureGather(x_20, x_10, coords123.xy, "
+            "i32(round(coords123.z)), vec2<i32>(3, 4))"},
+        // OpImageGather 2DDepth Array ConstOffset unsigned
+        ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1 ConstOffset "
+                        "%u_offsets2d",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_2d_array;)",
+                        "textureGather(x_20, x_10, coords123.xy, "
+                        "i32(round(coords123.z)), "
+                        "vec2<i32>(vec2<u32>(3u, 4u)))"},
+        // OpImageGather DepthCube
+        ImageAccessCase{"%float Cube 1 0 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords123 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_cube;)",
+                        "textureGather(x_20, x_10, coords123)"},
+        // OpImageGather DepthCube Array
+        ImageAccessCase{"%float Cube 1 1 0 1 Unknown",
+                        "%result = OpImageGather "
+                        "%v4float %sampled_image %coords1234 %int_1",
+                        R"([[group(0), binding(0)]] var x_10 : sampler;
+
+[[group(2), binding(1)]] var x_20 : texture_depth_cube_array;)",
+                        "textureGather(x_20, x_10, coords1234.xyz, "
+                        "i32(round(coords1234.w)))"}}));
 
 INSTANTIATE_TEST_SUITE_P(
-    DISABLED_ImageDrefGather,
+    ImageDrefGather,
     SpvParserHandleTest_SampledImageAccessTest,
     ::testing::ValuesIn(std::vector<ImageAccessCase>{
-        // TODO(dneto): OpImageDrefGather
-        // TODO(dneto): OpImageDrefGather with ConstOffset (signed and
-        // unsigned)
-        // TODO(dneto): OpImageDrefGather with Offset (signed and unsigned)
-        // TODO(dneto): OpImageDrefGather with Offsets (signed and unsigned)
+        // TODO(dneto): OpImageDrefGather 2DDepth
+        // TODO(dneto): OpImageDrefGather 2DDepth ConstOffset signed
+        // TODO(dneto): OpImageDrefGather 2DDepth ConstOffset unsigned
+        // TODO(dneto): OpImageDrefGather 2DDepth Array
+        // TODO(dneto): OpImageDrefGather 2DDepth Array ConstOffset signed
+        // TODO(dneto): OpImageDrefGather 2DDepth Array ConstOffset unsigned
+        // TODO(dneto): OpImageDrefGather DepthCube
+        // TODO(dneto): OpImageDrefGather DepthCube Array
     }));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -3458,21 +3604,21 @@
         {"%uint 2D 0 0 0 1 Unknown",
          "%result = OpImageFetch %v4uint %sampled_image %vf12 ConstOffset "
          "%the_vu12",
-         "ConstOffset is only permitted for sampling operations: ",
+         "ConstOffset is only permitted for sampling, gather, or "
+         "depth-reference gather operations: ",
          {}},
         // ImageRead
         {"%uint 2D 0 0 0 2 Rgba32ui",
          "%result = OpImageRead %v4uint %im %vu12 ConstOffset %the_vu12",
-         "ConstOffset is only permitted for sampling operations: ",
+         "ConstOffset is only permitted for sampling, gather, or "
+         "depth-reference gather operations: ",
          {}},
         // ImageWrite
         {"%uint 2D 0 0 0 2 Rgba32ui",
          "OpImageWrite %im %vu12 %vu1234 ConstOffset %the_vu12",
-         "ConstOffset is only permitted for sampling operations: ",
-         {}}
-        // TODO(dneto): Gather
-        // TODO(dneto): DrefGather
-    }));
+         "ConstOffset is only permitted for sampling, gather, or "
+         "depth-reference gather operations: ",
+         {}}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ConstOffset_BadDim_Errors,