spirv-reader: split off last coordinate for arrayed texture access

Also ensure that the number of texture coordinates is exactly
the right number required for the given texture dimension.
I think SPIR-V is looser in this respect.

Assumes coordinates are floating point.

Bug: tint:349
Change-Id: I4512c333fada3647c66f13ef31897b2d73b46cf0
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/33982
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 15286e8..0f64d8e 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -16,6 +16,7 @@
 
 #include <algorithm>
 #include <array>
+#include <cassert>
 #include <sstream>
 #include <unordered_map>
 #include <unordered_set>
@@ -53,6 +54,7 @@
 #include "src/ast/switch_statement.h"
 #include "src/ast/type/bool_type.h"
 #include "src/ast/type/pointer_type.h"
+#include "src/ast/type/texture_type.h"
 #include "src/ast/type/type.h"
 #include "src/ast/type/u32_type.h"
 #include "src/ast/type/vector_type.h"
@@ -3630,7 +3632,6 @@
       parser_impl_.GetMemoryObjectDeclarationForHandle(sampled_image_id, false);
   const auto* image =
       parser_impl_.GetMemoryObjectDeclarationForHandle(sampled_image_id, true);
-
   if (!sampler) {
     return Fail() << "interal error: couldn't find sampler for "
                   << inst.PrettyPrint();
@@ -3646,12 +3647,18 @@
   params.push_back(
       create<ast::IdentifierExpression>(namer_.Name(sampler->result_id())));
 
-  // Push the coordinates operand.
+  // Push the coordinates operands.
   // TODO(dneto): For explicit-Lod variations, we may have to convert from
   // integral coordinates to floating point coordinates.
-  // TODO(dneto): For arrayed access, split off the array layer.
-  params.push_back(MakeOperand(inst, 1).expr);
-  uint32_t arg_index = 2;
+  // In WGSL, integer (unnormalized) coordinates are only used for texture
+  // fetch (textureLoad on sampled image) or textureLoad or textureStore
+  // on storage images.
+  auto coords = MakeCoordinateOperandsForImageAccess(inst);
+  if (coords.empty()) {
+    return false;
+  }
+  params.insert(params.end(), coords.begin(), coords.end());
+  uint32_t arg_index = 2;  // Skip over texture and coordinate params
   const auto num_args = inst.NumInOperands();
 
   std::string builtin_name;
@@ -3725,6 +3732,137 @@
   return EmitConstDefOrWriteToHoistedVar(inst, {result_type, call_expr});
 }
 
+ast::ExpressionList FunctionEmitter::MakeCoordinateOperandsForImageAccess(
+    const spvtools::opt::Instruction& inst) {
+  if (!parser_impl_.success()) {
+    Fail();
+    return {};
+  }
+  if (inst.NumInOperands() == 0) {
+    Fail() << "internal error: not an image access instruction: "
+           << inst.PrettyPrint();
+    return {};
+  }
+  const auto sampled_image_id = inst.GetSingleWordInOperand(0);
+  const auto* image =
+      parser_impl_.GetMemoryObjectDeclarationForHandle(sampled_image_id, true);
+  if (!image) {
+    Fail() << "internal error: couldn't find image for " << inst.PrettyPrint();
+    return {};
+  }
+  if (image->NumInOperands() < 1) {
+    Fail() << "image access is missing a coordinate parameter: "
+           << inst.PrettyPrint();
+    return {};
+  }
+
+  // The coordinates parameter is always in position 1.
+  TypedExpression raw_coords(MakeOperand(inst, 1));
+  if (!raw_coords.type) {
+    return {};
+  }
+  ast::type::PointerType* type = parser_impl_.GetTypeForHandleVar(*image);
+  if (!parser_impl_.success()) {
+    Fail();
+    return {};
+  }
+  if (!type || !type->type()->IsTexture()) {
+    Fail() << "invalid texture type for " << image->PrettyPrint();
+    return {};
+  }
+  ast::type::TextureDimension dim = type->type()->AsTexture()->dim();
+  // Number of regular coordinates.
+  uint32_t num_axes = 0;
+  bool is_arrayed = false;
+  switch (dim) {
+    case ast::type::TextureDimension::k1d:
+      num_axes = 1;
+      break;
+    case ast::type::TextureDimension::k1dArray:
+      num_axes = 1;
+      is_arrayed = true;
+      break;
+    case ast::type::TextureDimension::k2d:
+      num_axes = 2;
+      break;
+    case ast::type::TextureDimension::k2dArray:
+      num_axes = 2;
+      is_arrayed = true;
+      break;
+    case ast::type::TextureDimension::k3d:
+      num_axes = 3;
+      break;
+    case ast::type::TextureDimension::kCube:
+      // For cubes, 3 coordinates form a direction vector.
+      num_axes = 3;
+      break;
+    case ast::type::TextureDimension::kCubeArray:
+      // For cubes, 3 coordinates form a direction vector.
+      num_axes = 3;
+      is_arrayed = true;
+      break;
+    default:
+      Fail() << "unsupported image dimensionality for " << type->type_name()
+             << " prompted by " << inst.PrettyPrint();
+      return {};
+  }
+  assert(num_axes <= 3);
+  const auto num_coords_required = num_axes + (is_arrayed ? 1 : 0);
+  uint32_t num_coords_supplied = 0;
+  if (raw_coords.type->IsF32()) {
+    num_coords_supplied = 1;
+  } else if (raw_coords.type->IsVector()) {
+    num_coords_supplied = raw_coords.type->AsVector()->size();
+  }
+  if (num_coords_supplied == 0) {
+    Fail() << "bad or unsupported coordinate type for image access: "
+           << inst.PrettyPrint();
+    return {};
+  }
+  if (num_coords_required > num_coords_supplied) {
+    Fail() << "image access required " << num_coords_required
+           << " coordinate components, but only " << num_coords_supplied
+           << " provided, in: " << inst.PrettyPrint();
+    return {};
+  }
+
+  auto prefix_swizzle = [this](uint32_t i) {
+    const char* prefix_name[] = {"", "x", "xy", "xyz"};
+    return ast_module_.create<ast::IdentifierExpression>(prefix_name[i & 3]);
+  };
+
+  ast::ExpressionList result;
+
+  // TODO(dneto): Convert component type if needed.
+  if (is_arrayed) {
+    // The source must be a vector, because it has enough components and has an
+    // array component. Use a vector swizzle to get the first `num_axes`
+    // components.
+    result.push_back(ast_module_.create<ast::MemberAccessorExpression>(
+        raw_coords.expr, prefix_swizzle(num_axes)));
+
+    // Now get the array index.
+    ast::Expression* array_index =
+        ast_module_.create<ast::MemberAccessorExpression>(raw_coords.expr,
+                                                          Swizzle(num_axes));
+    // Convert it to an unsigned integer type.
+    result.push_back(ast_module_.create<ast::TypeConstructorExpression>(
+        ast_module_.create<ast::type::U32Type>(),
+        ast::ExpressionList{array_index}));
+  } else {
+    if (num_coords_supplied == num_coords_required) {
+      // Pass the value through.
+      result.push_back(std::move(raw_coords.expr));
+    } else {
+      // There are more coordinates supplied than needed. So the source type is
+      // a vector. Use a vector swizzle to get the first `num_axes` components.
+      result.push_back(ast_module_.create<ast::MemberAccessorExpression>(
+          raw_coords.expr, prefix_swizzle(num_axes)));
+    }
+  }
+  return result;
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index f5c3872..19fe372 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -679,6 +679,20 @@
   /// @returns the identifier expression for the @p i'th component
   ast::IdentifierExpression* Swizzle(uint32_t i);
 
+  /// Converts SPIR-V image coordinates from an image access instruction
+  /// (e.g. OpImageSampledImplicitLod) into an expression list consisting of
+  /// the texture coordinates, and an integral array index if the texture is
+  /// arrayed. The texture coordinate is a scalar for 1D textures, a vector of
+  /// 2 elements for a 2D texture, and a vector of 3 elements for a 3D or
+  /// Cube texture. Excess components are ignored, e.g. if the SPIR-V
+  /// coordinate is a 4-element vector but the image is a 2D non-arrayed
+  /// texture then the 3rd and 4th components are ignored.
+  /// On failure, issues an error and returns an empty expression list.
+  /// @param image_access the image access instruction
+  /// @returns an ExpressionList of the coordinate and array index (if any)
+  ast::ExpressionList MakeCoordinateOperandsForImageAccess(
+      const spvtools::opt::Instruction& image_access);
+
  private:
   /// @returns the store type for the OpVariable instruction, or
   /// null on failure.
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index cc9bd28..d0b86e0 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1600,8 +1600,16 @@
   }
 }
 
-ast::type::Type* ParserImpl::GetTypeForHandleVar(
+ast::type::PointerType* ParserImpl::GetTypeForHandleVar(
     const spvtools::opt::Instruction& var) {
+  if (!success()) {
+    return nullptr;
+  }
+  auto where = handle_type_.find(&var);
+  if (where != handle_type_.end()) {
+    return where->second;
+  }
+
   // The WGSL handle type is determined by looking at information from
   // several sources:
   //    - the usage of the handle by image access instructions
@@ -1637,7 +1645,7 @@
           << var.PrettyPrint();
       return nullptr;
     default:
-      Fail() << "invalid type for image or sampler variable "
+      Fail() << "invalid type for image or sampler variable: "
              << var.PrettyPrint();
       return nullptr;
   }
@@ -1774,8 +1782,11 @@
   }
 
   // Form the pointer type.
-  return ast_module_.create<ast::type::PointerType>(
+  auto* result = ast_module_.create<ast::type::PointerType>(
       ast_store_type, ast::StorageClass::kUniformConstant);
+  // Remember it for later.
+  handle_type_[&var] = result;
+  return result;
 }
 
 bool ParserImpl::RegisterHandleUsage() {
@@ -1931,6 +1942,11 @@
   return Usage();
 }
 
+const spvtools::opt::Instruction* ParserImpl::GetInstructionForTest(
+    uint32_t id) const {
+  return def_use_mgr_ ? def_use_mgr_->GetDef(id) : nullptr;
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index e25b138..b5e331d 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -115,6 +115,9 @@
     return fail_stream_;
   }
 
+  /// @return true if failure has not yet occurred
+  bool success() const { return success_; }
+
   /// @returns the accumulated error string
   const std::string error() { return errors_.str(); }
 
@@ -383,7 +386,7 @@
   /// @param id the SPIR-V result id.
   /// @return the Source record, or a default one
   Source GetSourceForResultIdForTest(uint32_t id) const;
-  /// Returns the soruce record for the given instruction.
+  /// Returns the source record for the given instruction.
   /// @param inst the SPIR-V instruction
   /// @return the Source record, or a default one
   Source GetSourceForInst(const spvtools::opt::Instruction* inst) const;
@@ -428,7 +431,13 @@
   /// @param var the OpVariable instruction
   /// @returns the Tint AST type for the poiner-to-{sampler|texture} or null on
   /// error
-  ast::type::Type* GetTypeForHandleVar(const spvtools::opt::Instruction& var);
+  ast::type::PointerType* GetTypeForHandleVar(
+      const spvtools::opt::Instruction& var);
+
+  /// Returns the SPIR-V instruction with the given ID, or nullptr.
+  /// @param id the SPIR-V result ID
+  /// @returns the instruction, or nullptr on error
+  const spvtools::opt::Instruction* GetInstructionForTest(uint32_t id) const;
 
  private:
   /// Converts a specific SPIR-V type to a Tint type. Integer case
@@ -580,6 +589,9 @@
   // Maps a memory-object-declaration instruction to any sampler or texture
   // usages implied by usages of the memory-object-declaration.
   std::unordered_map<const spvtools::opt::Instruction*, Usage> handle_usage_;
+  // The inferred pointer type for the given handle variable.
+  std::unordered_map<const spvtools::opt::Instruction*, ast::type::PointerType*>
+      handle_type_;
 };
 
 }  // namespace spirv
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
index 0b3770a..813a3c7 100644
--- a/src/reader/spirv/parser_impl_handle_test.cc
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include <string>
+#include <vector>
 
 #include "gmock/gmock.h"
 #include "src/reader/spirv/function.h"
@@ -39,7 +40,7 @@
   )";
 }
 
-std::string CommonTypes() {
+std::string CommonBasicTypes() {
   return R"(
     %void = OpTypeVoid
     %voidfn = OpTypeFunction %void
@@ -50,6 +51,7 @@
 
     %int_3 = OpConstant %uint 3
     %int_4 = OpConstant %uint 4
+    %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
     %uint_2 = OpConstant %uint 2
     %uint_100 = OpConstant %uint 100
@@ -63,13 +65,26 @@
     %v4float = OpTypeVector %float 4
 
     %float_null = OpConstantNull %float
+    %float_1 = OpConstant %float 1
+    %float_2 = OpConstant %float 2
+    %float_3 = OpConstant %float 3
+    %float_4 = OpConstant %float 4
     %float_7 = OpConstant %float 7
     %v2float_null = OpConstantNull %v2float
     %v3float_null = OpConstantNull %v3float
     %v4float_null = OpConstantNull %v4float
 
+    %vf12 = OpConstantComposite %v2float %float_1 %float_2
+    %vf123 = OpConstantComposite %v3float %float_1 %float_2 %float_3
+    %vf1234 = OpConstantComposite %v4float %float_1 %float_2 %float_3 %float_4
+
     %depth = OpConstant %float 0.2
     %offsets2d = OpConstantComposite %v2int %int_3 %int_4
+  )";
+}
+
+std::string CommonImageTypes() {
+  return R"(
 
 ; Define types for all sampler and texture types that can map to WGSL,
 ; modulo texel formats for storage textures. For now, we limit
@@ -194,6 +209,10 @@
   )";
 }
 
+std::string CommonTypes() {
+  return CommonBasicTypes() + CommonImageTypes();
+}
+
 TEST_F(SpvParserTest,
        GetMemoryObjectDeclarationForHandle_WellFormedButNotAHandle) {
   const auto assembly = Preamble() + CommonTypes() + R"(
@@ -1801,6 +1820,403 @@
             )
           })"}));
 
+struct ImageCoordsCase {
+  std::string spirv_image_type_details;  // SPIR-V image type, excluding result
+                                         // ID and opcode
+  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 << "ImageAccessCase(" << c.spirv_image_type_details << "\n"
+      << c.spirv_image_access << "\n";
+
+  for (auto e : c.expected_expressions) {
+    out << e << ",";
+  }
+  out << ")" << std::endl;
+  return out;
+}
+
+using SpvParserTest_ImageCoordsTest =
+    SpvParserTestBase<::testing::TestWithParam<ImageCoordsCase>>;
+
+TEST_P(SpvParserTest_ImageCoordsTest, MakeCoordinateOperandsForImageAccess) {
+  const auto assembly = Preamble() + R"(
+     OpEntryPoint Fragment %100 "main"
+     OpExecutionMode %100 OriginUpperLeft
+     OpName %float_var "float_var"
+     OpName %ptr_float "ptr_float"
+     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
+
+     %si_ty = OpTypeSampledImage %im_ty
+     %coords = OpConstantNull %v2float
+
+     %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
+
+     ; create some names that will become WGSL variables x_1, x_12, and so on.
+     %1 = OpCopyObject %float %float_1
+     %12 = OpCopyObject %v2float %vf12
+     %123 = OpCopyObject %v3float %vf123
+     %1234 = OpCopyObject %v4float %vf1234
+
+
+     %sam = OpLoad %sampler %10
+     %im = OpLoad %im_ty %20
+     %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));
+  if (!p->BuildAndParseInternalModule()) {
+    EXPECT_THAT(p->error(), Eq(GetParam().expected_error));
+  } else {
+    EXPECT_TRUE(p->error().empty()) << p->error();
+    FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
+    // We actually have to generate the module to cache expressions for the
+    // result IDs, particularly the OpCopyObject
+    fe.Emit();
+
+    const spvtools::opt::Instruction* anchor = p->GetInstructionForTest(1000);
+    ASSERT_NE(anchor, nullptr);
+    const spvtools::opt::Instruction& image_access = *(anchor->PreviousNode());
+
+    ast::ExpressionList result =
+        fe.MakeCoordinateOperandsForImageAccess(image_access);
+    if (GetParam().expected_error.empty()) {
+      EXPECT_TRUE(fe.success()) << p->error();
+      EXPECT_TRUE(p->error().empty());
+      std::vector<std::string> result_strings;
+      for (auto* expr : result) {
+        ASSERT_NE(expr, nullptr);
+        result_strings.push_back(expr->str());
+      }
+      EXPECT_THAT(result_strings,
+                  ::testing::ContainerEq(GetParam().expected_expressions));
+    } else {
+      EXPECT_FALSE(fe.success());
+      EXPECT_THAT(p->error(), Eq(GetParam().expected_error));
+      EXPECT_TRUE(result.empty());
+    }
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(Good_1D,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %1",
+                              "",
+                              {"Identifier[not set]{x_1}\n"}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %12",  // one excess arg
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_12}
+  Identifier[not set]{x}
+}
+)"}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %123",  // two excess args
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_123}
+  Identifier[not set]{x}
+}
+)"}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %1234",  // three excess args
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_1234}
+  Identifier[not set]{x}
+}
+)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_1DArray,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 1D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %12",
+                              "",
+                              {
+                                  R"(MemberAccessor[not set]{
+  Identifier[not set]{x_12}
+  Identifier[not set]{x}
+}
+)",
+                                  R"(TypeConstructor[not set]{
+  __u32
+  MemberAccessor[not set]{
+    Identifier[not set]{x_12}
+    Identifier[not set]{y}
+  }
+}
+)"}},
+                             {"%float 1D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %123",  // one excess arg
+                              "",
+                              {
+                                  R"(MemberAccessor[not set]{
+  Identifier[not set]{x_123}
+  Identifier[not set]{x}
+}
+)",
+                                  R"(TypeConstructor[not set]{
+  __u32
+  MemberAccessor[not set]{
+    Identifier[not set]{x_123}
+    Identifier[not set]{y}
+  }
+}
+)"}},
+                             {"%float 1D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %1234",  // two excess args
+                              "",
+                              {
+                                  R"(MemberAccessor[not set]{
+  Identifier[not set]{x_1234}
+  Identifier[not set]{x}
+}
+)",
+                                  R"(TypeConstructor[not set]{
+  __u32
+  MemberAccessor[not set]{
+    Identifier[not set]{x_1234}
+    Identifier[not set]{y}
+  }
+}
+)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_2D,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 2D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %12",
+                              "",
+                              {"Identifier[not set]{x_12}\n"}},
+                             {"%float 2D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %123",  // one excess arg
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_123}
+  Identifier[not set]{xy}
+}
+)"}},
+                             {"%float 2D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %1234",  // two excess args
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_1234}
+  Identifier[not set]{xy}
+}
+)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_2DArray,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 2D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %123",
+                              "",
+                              {
+                                  R"(MemberAccessor[not set]{
+  Identifier[not set]{x_123}
+  Identifier[not set]{xy}
+}
+)",
+                                  R"(TypeConstructor[not set]{
+  __u32
+  MemberAccessor[not set]{
+    Identifier[not set]{x_123}
+    Identifier[not set]{z}
+  }
+}
+)"}},
+                             {"%float 2D 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod %v4float "
+                              "%sampled_image %1234",  // one excess arg
+                              "",
+                              {
+                                  R"(MemberAccessor[not set]{
+  Identifier[not set]{x_1234}
+  Identifier[not set]{xy}
+}
+)",
+                                  R"(TypeConstructor[not set]{
+  __u32
+  MemberAccessor[not set]{
+    Identifier[not set]{x_1234}
+    Identifier[not set]{z}
+  }
+}
+)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_3D,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 3D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %123",
+                              "",
+                              {"Identifier[not set]{x_123}\n"}},
+                             {"%float 3D 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %1234",  // one excess
+                                                       // arg
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_1234}
+  Identifier[not set]{xyz}
+}
+)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_Cube,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float Cube 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %123",
+                              "",
+                              {"Identifier[not set]{x_123}\n"}},
+                             {"%float Cube 0 0 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %1234",  // one excess
+                                                       // arg
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_1234}
+  Identifier[not set]{xyz}
+}
+)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(Good_CubeArray,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float Cube 0 1 0 1 Unknown",
+                              "%result = OpImageSampleImplicitLod "
+                              "%v4float "
+                              "%sampled_image %1234",
+                              "",
+                              {R"(MemberAccessor[not set]{
+  Identifier[not set]{x_1234}
+  Identifier[not set]{xyz}
+}
+)",
+                               R"(TypeConstructor[not set]{
+  __u32
+  MemberAccessor[not set]{
+    Identifier[not set]{x_1234}
+    Identifier[not set]{w}
+  }
+}
+)"}}}));
+
+INSTANTIATE_TEST_SUITE_P(BadInstructions,
+                         SpvParserTest_ImageCoordsTest,
+                         ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "OpNop",
+                              "internal error: not an image access "
+                              "instruction: OpNop",
+                              {}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "%foo = OpCopyObject %float %float_1",
+                              "internal error: couldn't find image for "
+                              "%50 = OpCopyObject %6 %26",
+                              {}},
+                             {"%float 1D 0 0 0 1 Unknown",
+                              "OpStore %float_var %float_1",
+                              "invalid type for image or sampler "
+                              "variable: %2 = OpVariable %3 Function",
+                              {}},
+                             // An example with a missing coordinate
+                             // won't assemble, so we skip it.
+                         }));
+
+INSTANTIATE_TEST_SUITE_P(
+    Bad_Coordinate,
+    SpvParserTest_ImageCoordsTest,
+    ::testing::ValuesIn(std::vector<ImageCoordsCase>{
+        {"%float 2D 0 0 0 1 Unknown",
+         "%result = OpImageSampleImplicitLod "
+         // bad type for coordinate: not a number
+         "%v4float %sampled_image %float_var",
+         "bad or unsupported coordinate type for image access: %50 = "
+         "OpImageSampleImplicitLod %24 %49 %2",
+         {}},
+        {"%float 1D 0 1 0 1 Unknown",  // 1DArray
+         "%result = OpImageSampleImplicitLod "
+         // 1 component, but need 2
+         "%v4float %sampled_image %1",
+         "image access required 2 coordinate components, but only 1 provided, "
+         "in: %50 = OpImageSampleImplicitLod %24 %49 %1",
+         {}},
+        {"%float 2D 0 0 0 1 Unknown",  // 2D
+         "%result = OpImageSampleImplicitLod "
+         // 1 component, but need 2
+         "%v4float %sampled_image %1",
+         "image access required 2 coordinate components, but only 1 provided, "
+         "in: %50 = OpImageSampleImplicitLod %24 %49 %1",
+         {}},
+        {"%float 2D 0 1 0 1 Unknown",  // 2DArray
+         "%result = OpImageSampleImplicitLod "
+         // 2 component, but need 3
+         "%v4float %sampled_image %12",
+         "image access required 3 coordinate components, but only 2 provided, "
+         "in: %50 = OpImageSampleImplicitLod %24 %49 %12",
+         {}},
+        {"%float 3D 0 0 0 1 Unknown",  // 3D
+         "%result = OpImageSampleImplicitLod "
+         // 2 components, but need 3
+         "%v4float %sampled_image %12",
+         "image access required 3 coordinate components, but only 2 provided, "
+         "in: %50 = OpImageSampleImplicitLod %24 %49 %12",
+         {}},
+    }));
+
 }  // namespace
 }  // namespace spirv
 }  // namespace reader