spirv-reader: add GetMemoryObjectDeclarationForHandle

Bug: tint:109
Change-Id: Ifb437ce9a39db7f92ca081e7ea551a576b0ecb2b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/32740
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index b53cc1f..924e832 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -846,6 +846,7 @@
     "src/reader/spirv/parser_impl_convert_type_test.cc",
     "src/reader/spirv/parser_impl_function_decl_test.cc",
     "src/reader/spirv/parser_impl_get_decorations_test.cc",
+    "src/reader/spirv/parser_impl_handle_test.cc",
     "src/reader/spirv/parser_impl_import_test.cc",
     "src/reader/spirv/parser_impl_module_var_test.cc",
     "src/reader/spirv/parser_impl_named_types_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index cfd02d1..717a630 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -450,6 +450,7 @@
     reader/spirv/parser_impl_convert_type_test.cc
     reader/spirv/parser_impl_function_decl_test.cc
     reader/spirv/parser_impl_get_decorations_test.cc
+    reader/spirv/parser_impl_handle_test.cc
     reader/spirv/parser_impl_import_test.cc
     reader/spirv/parser_impl_module_var_test.cc
     reader/spirv/parser_impl_named_types_test.cc
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index 8caacf2..bcc95a1 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -1503,6 +1503,88 @@
   return success_;
 }
 
+const spvtools::opt::Instruction*
+ParserImpl::GetMemoryObjectDeclarationForHandle(uint32_t id,
+                                                bool follow_image) {
+  auto local_fail = [this, id,
+                     follow_image]() -> const spvtools::opt::Instruction* {
+    const auto* inst = def_use_mgr_->GetDef(id);
+    Fail() << "Could not find memory object declaration for the "
+           << (follow_image ? "image" : "sampler") << " underlying id " << id
+           << (inst ? inst->PrettyPrint() : std::string());
+    return nullptr;
+  };
+
+  auto& memo_table =
+      (follow_image ? mem_obj_decl_image_ : mem_obj_decl_sampler_);
+
+  // Use a visited set to defend against bad input which might have long
+  // chains or even loops.
+  std::unordered_set<uint32_t> visited;
+
+  // Trace backward in the SSA data flow until we hit a memory object
+  // declaration.
+  while (true) {
+    auto where = memo_table.find(id);
+    if (where != memo_table.end()) {
+      return where->second;
+    }
+    // Protect against loops.
+    auto visited_iter = visited.find(id);
+    if (visited_iter != visited.end()) {
+      // We've hit a loop. Mark all the visited nodes
+      // as dead ends.
+      for (auto iter : visited) {
+        memo_table[iter] = nullptr;
+      }
+      return nullptr;
+    }
+    visited.insert(id);
+
+    const auto* inst = def_use_mgr_->GetDef(id);
+    if (inst == nullptr) {
+      return local_fail();
+    }
+    switch (inst->opcode()) {
+      case SpvOpFunctionParameter:
+      case SpvOpVariable:
+        // We found the memory object declaration.
+        // Remember it as the answer for the whole path.
+        for (auto iter : visited) {
+          memo_table[iter] = inst;
+        }
+        return inst;
+      case SpvOpLoad:
+        // Follow the pointer being loaded
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpCopyObject:
+        // Follow the object being copied.
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpAccessChain:
+      case SpvOpInBoundsAccessChain:
+      case SpvOpPtrAccessChain:
+      case SpvOpInBoundsPtrAccessChain:
+        // Follow the base pointer.
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      case SpvOpSampledImage:
+        // Follow the image or the sampler, depending on the follow_image
+        // parameter.
+        id = inst->GetSingleWordInOperand(follow_image ? 0 : 1);
+        break;
+      case SpvOpImage:
+        // Follow the sampled image
+        id = inst->GetSingleWordInOperand(0);
+        break;
+      default:
+        // This is not valid.
+        return local_fail();
+    }
+  }
+}
+
 }  // namespace spirv
 }  // namespace reader
 }  // namespace tint
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index f446592..5f0d350a 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -375,6 +375,22 @@
     return scalar_spec_constants_.find(id) != scalar_spec_constants_.end();
   }
 
+  /// For a SPIR-V ID that defines a sampler, image, or sampled image value,
+  /// return the SPIR-V instruction that represents the memory object
+  /// declaration for the object.  If we encounter an OpSampledImage along the
+  /// way, follow the image operand when follow_image is true; otherwise follow
+  /// the sampler operand. Returns null and emits an error if it can't trace
+  /// back to a memory object declaration.
+  /// This method can be used any time after BuildInternalModule has been
+  /// invoked.
+  /// @param id the SPIR-V ID of the sampler, image, or sampled image
+  /// @param follow_image indicates whether to follow the image operand of
+  /// OpSampledImage
+  /// @returns the memory object declaration for the handle, or nullptr on error
+  const spvtools::opt::Instruction* GetMemoryObjectDeclarationForHandle(
+      uint32_t id,
+      bool follow_image);
+
  private:
   /// Converts a specific SPIR-V type to a Tint type. Integer case
   ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
@@ -497,6 +513,18 @@
   // Maps function_id to a list of entrypoint information
   std::unordered_map<uint32_t, std::vector<EntryPointInfo>>
       function_to_ep_info_;
+
+  // Maps from a SPIR-V ID to its underlying memory object declaration,
+  // following image paths. This a memoization table for
+  // GetMemoryObjectDeclarationForHandle. (A SPIR-V memory object declaration is
+  // an OpVariable or an OpFunctinParameter with pointer type).
+  std::unordered_map<uint32_t, const spvtools::opt::Instruction*>
+      mem_obj_decl_image_;
+  // Maps from a SPIR-V ID to its underlying memory object declaration,
+  // following sampler paths. This a memoization table for
+  // GetMemoryObjectDeclarationForHandle.
+  std::unordered_map<uint32_t, const spvtools::opt::Instruction*>
+      mem_obj_decl_sampler_;
 };
 
 }  // namespace spirv
diff --git a/src/reader/spirv/parser_impl_handle_test.cc b/src/reader/spirv/parser_impl_handle_test.cc
new file mode 100644
index 0000000..70d6afb
--- /dev/null
+++ b/src/reader/spirv/parser_impl_handle_test.cc
@@ -0,0 +1,749 @@
+// Copyright 2020 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gmock/gmock.h"
+#include "src/reader/spirv/function.h"
+#include "src/reader/spirv/parser_impl.h"
+#include "src/reader/spirv/parser_impl_test_helper.h"
+#include "src/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+std::string Preamble() {
+  return R"(
+    OpCapability Shader
+    OpCapability Sampled1D
+    OpCapability Image1D
+    OpCapability StorageImageExtendedFormats
+    OpMemoryModel Logical Simple
+  )";
+}
+
+std::string CommonTypes() {
+  return R"(
+    %void = OpTypeVoid
+    %voidfn = OpTypeFunction %void
+
+    %float = OpTypeFloat 32
+    %uint = OpTypeInt 32 0
+    %int = OpTypeInt 32 1
+
+    %uint_1 = OpConstant %uint 1
+    %uint_2 = OpConstant %uint 2
+    %uint_100 = OpConstant %uint 100
+
+    %v4uint = OpTypeVector %uint 4
+    %v4int = OpTypeVector %int 4
+    %v4float = OpTypeVector %float 4
+
+; Define types for all sampler and texture types that can map to WGSL,
+; modulo texel formats for storage textures. For now, we limit
+; ourselves to 2-channel 32-bit texel formats.
+
+; Because the SPIR-V reader also already generalizes so it can work with
+; combined image-samplers, we also test that too.
+
+    %sampler = OpTypeSampler
+
+    ; sampled images
+    %f_texture_1d          = OpTypeImage %float 1D   0 0 0 1 Unknown
+    %f_texture_1d_array    = OpTypeImage %float 1D   0 1 0 1 Unknown
+    %f_texture_2d          = OpTypeImage %float 2D   0 0 0 1 Unknown
+    %f_texture_2d_ms       = OpTypeImage %float 2D   0 0 1 1 Unknown
+    %f_texture_2d_array    = OpTypeImage %float 2D   0 1 0 1 Unknown
+    %f_texture_2d_ms_array = OpTypeImage %float 2D   0 1 1 1 Unknown ; not in WebGPU
+    %f_texture_3d          = OpTypeImage %float 3D   0 0 0 1 Unknown
+    %f_texture_cube        = OpTypeImage %float Cube 0 0 0 1 Unknown
+    %f_texture_cube_array  = OpTypeImage %float Cube 0 1 0 1 Unknown
+
+    ; storage images
+    %f_storage_1d         = OpTypeImage %float 1D   0 0 0 1 Rg32f
+    %f_storage_1d_array   = OpTypeImage %float 1D   0 1 0 1 Rg32f
+    %f_storage_2d         = OpTypeImage %float 2D   0 0 0 1 Rg32f
+    %f_storage_2d_array   = OpTypeImage %float 2D   0 1 0 1 Rg32f
+    %f_storage_3d         = OpTypeImage %float 3D   0 0 0 1 Rg32f
+
+    ; Now all the same, but for unsigned integer sampled type.
+
+    %u_texture_1d          = OpTypeImage %uint  1D   0 0 0 1 Unknown
+    %u_texture_1d_array    = OpTypeImage %uint  1D   0 1 0 1 Unknown
+    %u_texture_2d          = OpTypeImage %uint  2D   0 0 0 1 Unknown
+    %u_texture_2d_ms       = OpTypeImage %uint  2D   0 0 1 1 Unknown
+    %u_texture_2d_array    = OpTypeImage %uint  2D   0 1 0 1 Unknown
+    %u_texture_2d_ms_array = OpTypeImage %uint  2D   0 1 1 1 Unknown ; not in WebGPU
+    %u_texture_3d          = OpTypeImage %uint  3D   0 0 0 1 Unknown
+    %u_texture_cube        = OpTypeImage %uint  Cube 0 0 0 1 Unknown
+    %u_texture_cube_array  = OpTypeImage %uint  Cube 0 1 0 1 Unknown
+
+    %u_storage_1d         = OpTypeImage %uint  1D   0 0 0 1 Rg32ui
+    %u_storage_1d_array   = OpTypeImage %uint  1D   0 1 0 1 Rg32ui
+    %u_storage_2d         = OpTypeImage %uint  2D   0 0 0 1 Rg32ui
+    %u_storage_2d_array   = OpTypeImage %uint  2D   0 1 0 1 Rg32ui
+    %u_storage_3d         = OpTypeImage %uint  3D   0 0 0 1 Rg32ui
+
+    ; Now all the same, but for signed integer sampled type.
+
+    %i_texture_1d          = OpTypeImage %int  1D   0 0 0 1 Unknown
+    %i_texture_1d_array    = OpTypeImage %int  1D   0 1 0 1 Unknown
+    %i_texture_2d          = OpTypeImage %int  2D   0 0 0 1 Unknown
+    %i_texture_2d_ms       = OpTypeImage %int  2D   0 0 1 1 Unknown
+    %i_texture_2d_array    = OpTypeImage %int  2D   0 1 0 1 Unknown
+    %i_texture_2d_ms_array = OpTypeImage %int  2D   0 1 1 1 Unknown ; not in WebGPU
+    %i_texture_3d          = OpTypeImage %int  3D   0 0 0 1 Unknown
+    %i_texture_cube        = OpTypeImage %int  Cube 0 0 0 1 Unknown
+    %i_texture_cube_array  = OpTypeImage %int  Cube 0 1 0 1 Unknown
+
+    %i_storage_1d         = OpTypeImage %int  1D   0 0 0 1 Rg32i
+    %i_storage_1d_array   = OpTypeImage %int  1D   0 1 0 1 Rg32i
+    %i_storage_2d         = OpTypeImage %int  2D   0 0 0 1 Rg32i
+    %i_storage_2d_array   = OpTypeImage %int  2D   0 1 0 1 Rg32i
+    %i_storage_3d         = OpTypeImage %int  3D   0 0 0 1 Rg32i
+
+    ;; Now pointers to each of the above, so we can declare variables for them.
+
+    %ptr_sampler = OpTypePointer UniformConstant %sampler
+
+    %ptr_f_texture_1d          = OpTypePointer UniformConstant %f_texture_1d
+    %ptr_f_texture_1d_array    = OpTypePointer UniformConstant %f_texture_1d_array
+    %ptr_f_texture_2d          = OpTypePointer UniformConstant %f_texture_2d
+    %ptr_f_texture_2d_ms       = OpTypePointer UniformConstant %f_texture_2d_ms
+    %ptr_f_texture_2d_array    = OpTypePointer UniformConstant %f_texture_2d_array
+    %ptr_f_texture_2d_ms_array = OpTypePointer UniformConstant %f_texture_2d_ms_array
+    %ptr_f_texture_3d          = OpTypePointer UniformConstant %f_texture_3d
+    %ptr_f_texture_cube        = OpTypePointer UniformConstant %f_texture_cube
+    %ptr_f_texture_cube_array  = OpTypePointer UniformConstant %f_texture_cube_array
+
+    ; storage images
+    %ptr_f_storage_1d         = OpTypePointer UniformConstant %f_storage_1d
+    %ptr_f_storage_1d_array   = OpTypePointer UniformConstant %f_storage_1d_array
+    %ptr_f_storage_2d         = OpTypePointer UniformConstant %f_storage_2d
+    %ptr_f_storage_2d_array   = OpTypePointer UniformConstant %f_storage_2d_array
+    %ptr_f_storage_3d         = OpTypePointer UniformConstant %f_storage_3d
+
+    ; Now all the same, but for unsigned integer sampled type.
+
+    %ptr_u_texture_1d          = OpTypePointer UniformConstant %u_texture_1d
+    %ptr_u_texture_1d_array    = OpTypePointer UniformConstant %u_texture_1d_array
+    %ptr_u_texture_2d          = OpTypePointer UniformConstant %u_texture_2d
+    %ptr_u_texture_2d_ms       = OpTypePointer UniformConstant %u_texture_2d_ms
+    %ptr_u_texture_2d_array    = OpTypePointer UniformConstant %u_texture_2d_array
+    %ptr_u_texture_2d_ms_array = OpTypePointer UniformConstant %u_texture_2d_ms_array
+    %ptr_u_texture_3d          = OpTypePointer UniformConstant %u_texture_3d
+    %ptr_u_texture_cube        = OpTypePointer UniformConstant %u_texture_cube
+    %ptr_u_texture_cube_array  = OpTypePointer UniformConstant %u_texture_cube_array
+
+    %ptr_u_storage_1d         = OpTypePointer UniformConstant %u_storage_1d
+    %ptr_u_storage_1d_array   = OpTypePointer UniformConstant %u_storage_1d_array
+    %ptr_u_storage_2d         = OpTypePointer UniformConstant %u_storage_2d
+    %ptr_u_storage_2d_array   = OpTypePointer UniformConstant %u_storage_2d_array
+    %ptr_u_storage_3d         = OpTypePointer UniformConstant %u_storage_3d
+
+    ; Now all the same, but for signed integer sampled type.
+
+    %ptr_i_texture_1d          = OpTypePointer UniformConstant %i_texture_1d
+    %ptr_i_texture_1d_array    = OpTypePointer UniformConstant %i_texture_1d_array
+    %ptr_i_texture_2d          = OpTypePointer UniformConstant %i_texture_2d
+    %ptr_i_texture_2d_ms       = OpTypePointer UniformConstant %i_texture_2d_ms
+    %ptr_i_texture_2d_array    = OpTypePointer UniformConstant %i_texture_2d_array
+    %ptr_i_texture_2d_ms_array = OpTypePointer UniformConstant %i_texture_2d_ms_array
+    %ptr_i_texture_3d          = OpTypePointer UniformConstant %i_texture_3d
+    %ptr_i_texture_cube        = OpTypePointer UniformConstant %i_texture_cube
+    %ptr_i_texture_cube_array  = OpTypePointer UniformConstant %i_texture_cube_array
+
+    %ptr_i_storage_1d         = OpTypePointer UniformConstant %i_storage_1d
+    %ptr_i_storage_1d_array   = OpTypePointer UniformConstant %i_storage_1d_array
+    %ptr_i_storage_2d         = OpTypePointer UniformConstant %i_storage_2d
+    %ptr_i_storage_2d_array   = OpTypePointer UniformConstant %i_storage_2d_array
+    %ptr_i_storage_3d         = OpTypePointer UniformConstant %i_storage_3d
+
+  )";
+}
+
+TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_Variable_Direct) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_Variable_AccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  const auto assembly = Preamble() + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_Variable_InBoundsAccessChain) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpInBoundsAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpInBoundsAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_Variable_PtrAccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  const auto assembly = Preamble() + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_Variable_InBoundsPtrAccessChain) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %10 = OpVariable %ptr_sampler_array UniformConstant
+     %20 = OpVariable %ptr_image_array UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpInBoundsPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpInBoundsPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_Variable_CopyObject) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpCopyObject %ptr_sampler %10
+     %120 = OpCopyObject %ptr_f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_Variable_Load) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %110 = OpLoad %sampler %10
+     %120 = OpLoad %f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_Variable_SampledImage) {
+  // Trace through the sampled image instruction, but in two different
+  // directions.
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(100, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(100, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_Variable_Image) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %10 = OpVariable %ptr_sampler UniformConstant
+     %20 = OpVariable %ptr_f_texture_1d UniformConstant
+
+     %main = OpFunction %void None %voidfn
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+     %200 = OpImage %im %100
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(200, true);
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_FuncParam_Direct) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+     OpReturn
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(10, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(20, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_AccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_InBoundsAccessChain) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpInBoundsAccessChain %ptr_sampler %10 %uint_1
+     %120 = OpInBoundsAccessChain %ptr_f_texture_1d %20 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_PtrAccessChain) {
+  // Show that we would generalize to arrays of handles, even though that
+  // is not supported in WGSL MVP.
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_InBoundsPtrAccessChain) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampler_array = OpTypeArray %sampler %uint_100
+     %image_array = OpTypeArray %f_texture_1d %uint_100
+
+     %ptr_sampler_array = OpTypePointer UniformConstant %sampler_array
+     %ptr_image_array = OpTypePointer UniformConstant %image_array
+
+     %fty = OpTypeFunction %void %ptr_sampler_array %ptr_image_array
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler_array
+     %20 = OpFunctionParameter %ptr_image_array
+     %entry = OpLabel
+
+     %110 = OpInBoundsPtrAccessChain %ptr_sampler %10 %uint_1 %uint_1
+     %120 = OpInBoundsPtrAccessChain %ptr_f_texture_1d %20 %uint_1 %uint_2
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_CopyObject) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %110 = OpCopyObject %ptr_sampler %10
+     %120 = OpCopyObject %ptr_f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_FuncParam_Load) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %110 = OpLoad %sampler %10
+     %120 = OpLoad %f_texture_1d %20
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(110, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(120, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest,
+       GetMemoryObjectDeclarationForHandle_FuncParam_SampledImage) {
+  // Trace through the sampled image instruction, but in two different
+  // directions.
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+  const auto* sampler = p->GetMemoryObjectDeclarationForHandle(100, false);
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(100, true);
+
+  ASSERT_TRUE(sampler != nullptr);
+  EXPECT_EQ(sampler->result_id(), 10u);
+
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+TEST_F(SpvParserTest, GetMemoryObjectDeclarationForHandle_FuncParam_Image) {
+  const auto assembly = Preamble() + CommonTypes() + R"(
+     %sampled_image_type = OpTypeSampledImage %f_texture_1d
+
+     %fty = OpTypeFunction %void %ptr_sampler %ptr_f_texture_1d
+
+     %func = OpFunction %void None %fty
+     %10 = OpFunctionParameter %ptr_sampler
+     %20 = OpFunctionParameter %ptr_f_texture_1d
+     %entry = OpLabel
+
+     %s = OpLoad %sampler %10
+     %im = OpLoad %f_texture_1d %20
+     %100 = OpSampledImage %sampled_image_type %im %s
+     %200 = OpImage %im %100
+
+     OpReturn
+     OpFunctionEnd
+  )";
+  auto* p = parser(test::Assemble(assembly));
+  ASSERT_TRUE(p->BuildInternalModule());
+  EXPECT_TRUE(p->error().empty());
+
+  const auto* image = p->GetMemoryObjectDeclarationForHandle(200, true);
+  ASSERT_TRUE(image != nullptr);
+  EXPECT_EQ(image->result_id(), 20u);
+}
+
+}  // namespace
+}  // namespace spirv
+}  // namespace reader
+}  // namespace tint