spirv-reader: Restrict use of ConstOffset

Only permitted for image sampling (and later gather).

Only permitted for 2D, 2D Array, and 3D textures

Fixed: tint:408
Change-Id: Ib97bd17e45046ec8a2147b46899bf52ad9a8f883
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/35980
Auto-Submit: David Neto <dneto@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 97907ab..92f83e5 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -501,6 +501,22 @@
 }
 
 // @param opcode a SPIR-V opcode
+// @returns true if the given instruction is an image sampling operation.
+bool IsImageSampling(SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+      return true;
+    default:
+      // WGSL doesn't have *Proj* texturing.
+      break;
+  }
+  return false;
+}
+
+// @param opcode a SPIR-V opcode
 // @returns true if the given instruction is an image access instruction
 // whose first input operand is an OpImage value.
 bool IsRawImageAccess(SpvOp opcode) {
@@ -3989,7 +4005,8 @@
   params.push_back(create<ast::IdentifierExpression>(
       Source{}, ast_module_.RegisterSymbol(name), name));
 
-  if (IsSampledImageAccess(inst.opcode())) {
+  const auto opcode = inst.opcode();
+  if (IsSampledImageAccess(opcode)) {
     // Form the sampler operand.
     const auto* sampler = parser_impl_.GetMemoryObjectDeclarationForHandle(
         image_or_sampled_image_operand_id, false);
@@ -4029,7 +4046,7 @@
 
   std::string builtin_name;
   bool use_load_suffix = true;
-  switch (inst.opcode()) {
+  switch (opcode) {
     case SpvOpImageSampleImplicitLod:
     case SpvOpImageSampleExplicitLod:
       builtin_name = "textureSample";
@@ -4121,12 +4138,28 @@
   }
   if (arg_index < num_args &&
       (image_operands_mask & SpvImageOperandsConstOffsetMask)) {
+    if (!IsImageSampling(opcode)) {
+      return Fail() << "ConstOffset is only permitted for sampling operations: "
+                    << inst.PrettyPrint();
+    }
+    switch (texture_type->dim()) {
+      case ast::type::TextureDimension::k2d:
+      case ast::type::TextureDimension::k2dArray:
+      case ast::type::TextureDimension::k3d:
+        break;
+      default:
+        return Fail() << "ConstOffset is only permitted for 2D, 2D Arrayed, "
+                         "and 3D textures: "
+                      << inst.PrettyPrint();
+    }
+
     params.push_back(ToSignedIfUnsigned(MakeOperand(inst, arg_index)).expr);
     image_operands_mask ^= SpvImageOperandsConstOffsetMask;
     arg_index++;
   }
   if (arg_index < num_args &&
       (image_operands_mask & SpvImageOperandsSampleMask)) {
+    // TODO(dneto): only permitted with ImageFetch
     params.push_back(ToI32(MakeOperand(inst, arg_index)).expr);
     image_operands_mask ^= SpvImageOperandsSampleMask;
     arg_index++;
@@ -4184,7 +4217,7 @@
       value = create<ast::BitcastExpression>(Source{}, result_type, call_expr);
     }
     if (!expected_component_type->Is<ast::type::F32>() &&
-        IsSampledImageAccess(inst.opcode())) {
+        IsSampledImageAccess(opcode)) {
       // WGSL permits sampled image access only on float textures.
       // Reject this case in the SPIR-V reader, at least until SPIR-V validation
       // catches up with this rule and can reject it earlier in the workflow.
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index 1cf0f1f..d4ad95a 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -30,6 +30,7 @@
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Not;
+using ::testing::StartsWith;
 
 std::string Preamble() {
   return R"(
@@ -2617,28 +2618,6 @@
         Identifier[not set]{vi12}
         Identifier[not set]{vf1234}
       )
-    })"},
-        // OpImageWrite with ConstOffset
-        {"%float 2D 0 0 0 2 Rgba32f",
-         "OpImageWrite %im %vi12 %vf1234 ConstOffset %offsets2d",
-         R"(
-  Variable{
-    Decorations{
-      SetDecoration{2}
-      BindingDecoration{1}
-    }
-    x_20
-    uniform_constant
-    __storage_texture_write_only_2d_rgba32float
-  })",
-         R"(Call[not set]{
-      Identifier[not set]{textureStore}
-      (
-        Identifier[not set]{x_20}
-        Identifier[not set]{vi12}
-        Identifier[not set]{vf1234}
-        Identifier[not set]{offsets2d}
-      )
     })"}}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -3085,35 +3064,6 @@
           }
         }
       }
-    })"},
-        // OpImageRead with ConstOffset
-        {"%float 2D 0 0 0 2 Rgba32f",
-         "%99 = OpImageRead %v4float %im %vi12 ConstOffset %offsets2d",
-         R"(Variable{
-    Decorations{
-      SetDecoration{2}
-      BindingDecoration{1}
-    }
-    x_20
-    uniform_constant
-    __storage_texture_read_only_2d_rgba32float
-  })",
-         R"(VariableDeclStatement{
-      VariableConst{
-        x_99
-        none
-        __vec_4__f32
-        {
-          Call[not set]{
-            Identifier[not set]{textureLoad}
-            (
-              Identifier[not set]{x_20}
-              Identifier[not set]{vi12}
-              Identifier[not set]{offsets2d}
-            )
-          }
-        }
-      }
     })"}}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -3147,36 +3097,6 @@
         }
       }
     })"},
-        // OpImageFetch with ConstOffset
-        // TODO(dneto): Seems this is not valid in WGSL.
-        {"%float 2D 0 0 0 1 Unknown",
-         "%99 = OpImageFetch %v4float %im %vi12 ConstOffset %offsets2d",
-         R"(Variable{
-    Decorations{
-      SetDecoration{2}
-      BindingDecoration{1}
-    }
-    x_20
-    uniform_constant
-    __sampled_texture_2d__f32
-  })",
-         R"(VariableDeclStatement{
-      VariableConst{
-        x_99
-        none
-        __vec_4__f32
-        {
-          Call[not set]{
-            Identifier[not set]{textureLoad}
-            (
-              Identifier[not set]{x_20}
-              Identifier[not set]{vi12}
-              Identifier[not set]{offsets2d}
-            )
-          }
-        }
-      }
-    })"},
         // OpImageFetch with explicit level
         {"%float 2D 0 0 0 1 Unknown",
          "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
@@ -3760,7 +3680,7 @@
   )";
   auto p = parser(test::Assemble(assembly));
   if (!p->BuildAndParseInternalModule()) {
-    EXPECT_THAT(p->error(), Eq(GetParam().expected_error)) << assembly;
+    EXPECT_THAT(p->error(), StartsWith(GetParam().expected_error)) << assembly;
   } else {
     EXPECT_TRUE(p->error().empty()) << p->error();
     FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
@@ -4456,6 +4376,59 @@
          "sampled image must have float component type",
          {}}}));
 
+INSTANTIATE_TEST_SUITE_P(
+    ConstOffset_BadInstruction_Errors,
+    SpvParserTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // ImageFetch
+        {"%uint 2D 0 0 0 1 Unknown",
+         "%result = OpImageFetch %v4uint %sampled_image %vf12 ConstOffset "
+         "%the_vu12",
+         "ConstOffset is only permitted for sampling operations: ",
+         {}},
+        // ImageRead
+        {"%uint 2D 0 0 0 2 Rgba32ui",
+         "%result = OpImageRead %v4uint %im %vu12 ConstOffset %the_vu12",
+         "ConstOffset is only permitted for sampling 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
+    }));
+
+INSTANTIATE_TEST_SUITE_P(
+    ConstOffset_BadDim_Errors,
+    SpvParserTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        // 1D
+        {"%uint 1D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
+         "ConstOffset %the_vu12",
+         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
+         {}},
+        // 1D Array
+        {"%uint 1D 0 1 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
+         "ConstOffset %the_vu12",
+         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
+         {}},
+        // Cube
+        {"%uint Cube 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
+         "ConstOffset %the_vu12",
+         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
+         {}},
+        // Cube Array
+        {"%uint Cube 0 1 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod %v4float %sampled_image %vf1234 "
+         "ConstOffset %the_vu12",
+         "ConstOffset is only permitted for 2D, 2D Arrayed, and 3D textures: ",
+         {}}}));
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader