[inspector] Extract Sampler resource binding information

Adds in method to get resource binding information for non-comparison
samplers along with tests. Additionally some of the infrastructure for
implementing an equivalent comparison sampler code path is included.

BUG=tint:257

Change-Id: I5775bbd6233e1014bea54c5281d460544ab8383d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/31920
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/ast/function.cc b/src/ast/function.cc
index 23bd004..a90b085 100644
--- a/src/ast/function.cc
+++ b/src/ast/function.cc
@@ -167,6 +167,16 @@
   return ret;
 }
 
+const std::vector<std::pair<Variable*, Function::BindingInfo>>
+Function::referenced_sampler_variables() const {
+  return ReferencedSamplerVariablesImpl(type::SamplerKind::kSampler);
+}
+
+const std::vector<std::pair<Variable*, Function::BindingInfo>>
+Function::referenced_comparison_sampler_variables() const {
+  return ReferencedSamplerVariablesImpl(type::SamplerKind::kComparisonSampler);
+}
+
 void Function::add_ancestor_entry_point(const std::string& ep) {
   for (const auto& point : ancestor_entry_points_) {
     if (point == ep) {
@@ -253,5 +263,34 @@
   return out.str();
 }
 
+const std::vector<std::pair<Variable*, Function::BindingInfo>>
+Function::ReferencedSamplerVariablesImpl(type::SamplerKind kind) const {
+  std::vector<std::pair<Variable*, Function::BindingInfo>> ret;
+
+  for (auto* var : referenced_module_variables()) {
+    auto* unwrapped_type = var->type()->UnwrapIfNeeded();
+    if (!var->IsDecorated() || !unwrapped_type->IsSampler() ||
+        unwrapped_type->AsSampler()->kind() != kind) {
+      continue;
+    }
+
+    BindingDecoration* binding = nullptr;
+    SetDecoration* set = nullptr;
+    for (const auto& deco : var->AsDecorated()->decorations()) {
+      if (deco->IsBinding()) {
+        binding = deco->AsBinding();
+      } else if (deco->IsSet()) {
+        set = deco->AsSet();
+      }
+    }
+    if (binding == nullptr || set == nullptr) {
+      continue;
+    }
+
+    ret.push_back({var, BindingInfo{binding, set}});
+  }
+  return ret;
+}
+
 }  // namespace ast
 }  // namespace tint
diff --git a/src/ast/function.h b/src/ast/function.h
index 52cff49..e07658f 100644
--- a/src/ast/function.h
+++ b/src/ast/function.h
@@ -32,6 +32,7 @@
 #include "src/ast/pipeline_stage.h"
 #include "src/ast/set_decoration.h"
 #include "src/ast/statement.h"
+#include "src/ast/type/sampler_type.h"
 #include "src/ast/type/type.h"
 #include "src/ast/variable.h"
 
@@ -138,6 +139,16 @@
   /// @returns the referenced storagebuffers
   const std::vector<std::pair<Variable*, Function::BindingInfo>>
   referenced_storagebuffer_variables() const;
+  /// Retrieves any referenced regular Sampler variables. Note, the
+  /// storagebuffer must be decorated with both binding and set decorations.
+  /// @returns the referenced storagebuffers
+  const std::vector<std::pair<Variable*, Function::BindingInfo>>
+  referenced_sampler_variables() const;
+  /// Retrieves any referenced comparison Sampler variables. Note, the
+  /// storagebuffer must be decorated with both binding and set decorations.
+  /// @returns the referenced storagebuffers
+  const std::vector<std::pair<Variable*, Function::BindingInfo>>
+  referenced_comparison_sampler_variables() const;
 
   /// Adds an ancestor entry point
   /// @param ep the entry point ancestor
@@ -183,6 +194,8 @@
 
  private:
   Function(const Function&) = delete;
+  const std::vector<std::pair<Variable*, Function::BindingInfo>>
+  ReferencedSamplerVariablesImpl(type::SamplerKind kind) const;
 
   std::string name_;
   VariableList params_;
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index 7eb1aa7..43d11f7 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -187,6 +187,30 @@
   return GetStorageBufferResourceBindingsImpl(entry_point, true);
 }
 
+std::vector<ResourceBinding> Inspector::GetSamplerResourceBindings(
+    const std::string& entry_point) {
+  auto* func = FindEntryPointByName(entry_point);
+  if (!func) {
+    return {};
+  }
+
+  std::vector<ResourceBinding> result;
+
+  for (auto& rs : func->referenced_sampler_variables()) {
+    ResourceBinding entry;
+    ast::Variable* var = nullptr;
+    ast::Function::BindingInfo binding_info;
+    std::tie(var, binding_info) = rs;
+
+    entry.bind_group = binding_info.set->value();
+    entry.binding = binding_info.binding->value();
+
+    result.push_back(std::move(entry));
+  }
+
+  return result;
+}
+
 ast::Function* Inspector::FindEntryPointByName(const std::string& name) {
   auto* func = module_.FindFunctionByName(name);
   if (!func) {
@@ -211,11 +235,11 @@
   }
 
   std::vector<ResourceBinding> result;
-  for (auto& ruv : func->referenced_storagebuffer_variables()) {
+  for (auto& rsv : func->referenced_storagebuffer_variables()) {
     ResourceBinding entry;
     ast::Variable* var = nullptr;
     ast::Function::BindingInfo binding_info;
-    std::tie(var, binding_info) = ruv;
+    std::tie(var, binding_info) = rsv;
     if (!var->type()->IsAccessControl()) {
       continue;
     }
diff --git a/src/inspector/inspector.h b/src/inspector/inspector.h
index 2a64b3b..d2e39a8 100644
--- a/src/inspector/inspector.h
+++ b/src/inspector/inspector.h
@@ -68,14 +68,16 @@
   std::vector<ResourceBinding> GetStorageBufferResourceBindings(
       const std::string& entry_point);
 
-  // TODO(rharrison): Implement once
-  //                  https://dawn-review.googlesource.com/c/tint/+/31060 lands.
   /// @param entry_point name of the entry point to get information about.
-  /// @returns an empty vector and sets the error string. In the future it will
-  ///          return of all of the bindings for read-only storage buffers.
+  /// @returns vector of all of the bindings for read-only storage buffers.
   std::vector<ResourceBinding> GetReadOnlyStorageBufferResourceBindings(
       const std::string& entry_point);
 
+  /// @param entry_point name of the entry point to get information about.
+  /// @returns vector of all of the bindings for regular samplers.
+  std::vector<ResourceBinding> GetSamplerResourceBindings(
+      const std::string& entry_point);
+
  private:
   const ast::Module& module_;
   std::string error_;
diff --git a/src/inspector/inspector_test.cc b/src/inspector/inspector_test.cc
index 3993b2d..5edfb12 100644
--- a/src/inspector/inspector_test.cc
+++ b/src/inspector/inspector_test.cc
@@ -46,6 +46,8 @@
 #include "src/ast/type/i32_type.h"
 #include "src/ast/type/matrix_type.h"
 #include "src/ast/type/pointer_type.h"
+#include "src/ast/type/sampled_texture_type.h"
+#include "src/ast/type/sampler_type.h"
 #include "src/ast/type/struct_type.h"
 #include "src/ast/type/type.h"
 #include "src/ast/type/u32_type.h"
@@ -67,7 +69,9 @@
  public:
   InspectorHelper()
       : td_(std::make_unique<TypeDeterminer>(&ctx_, &mod_)),
-        inspector_(std::make_unique<Inspector>(mod_)) {}
+        inspector_(std::make_unique<Inspector>(mod_)),
+        sampler_type_(ast::type::SamplerKind::kSampler),
+        comparison_sampler_type_(ast::type::SamplerKind::kComparisonSampler) {}
 
   /// Generates an empty function
   /// @param name name of the function created
@@ -368,7 +372,7 @@
     AddBinding(name, type, ast::StorageClass::kUniform, set, binding);
   }
 
-  /// Adds an storage buffer variable to the module
+  /// Adds a storage buffer variable to the module
   /// @param name the name of the variable
   /// @param type the type to use
   /// @param set the binding group/set to use for the storage buffer
@@ -420,6 +424,89 @@
     return func;
   }
 
+  /// Adds a regular sampler variable to the module
+  /// @param name the name of the variable
+  /// @param set the binding group/set to use for the storage buffer
+  /// @param binding the binding number to use for the storage buffer
+  void AddSampler(const std::string& name, uint32_t set, uint32_t binding) {
+    AddBinding(name, sampler_type(), ast::StorageClass::kUniformConstant, set,
+               binding);
+  }
+
+  /// Adds a comparison sampler variable to the module
+  /// @param name the name of the variable
+  /// @param set the binding group/set to use for the storage buffer
+  /// @param binding the binding number to use for the storage buffer
+  void AddComparisonSampler(const std::string& name,
+                            uint32_t set,
+                            uint32_t binding) {
+    AddBinding(name, comparison_sampler_type(),
+               ast::StorageClass::kUniformConstant, set, binding);
+  }
+
+  /// Generates a SampledTextureType appropriate for the params
+  /// @param dim the dimensions of the texture
+  /// @param type the data type of the sampled texture
+  /// @returns the generated SampleTextureType
+  std::unique_ptr<ast::type::SampledTextureType> MakeSampledTextureType(
+      ast::type::TextureDimension dim,
+      ast::type::Type* type) {
+    return std::make_unique<ast::type::SampledTextureType>(dim, type);
+  }
+
+  /// Adds a sampled texture variable to the module
+  /// @param name the name of the variable
+  /// @param type the type to use
+  /// @param set the binding group/set to use for the sampled texture
+  /// @param binding the binding number to use for the sampled texture
+  void AddSampledTexture(const std::string& name,
+                         ast::type::Type* type,
+                         uint32_t set,
+                         uint32_t binding) {
+    AddBinding(name, type, ast::StorageClass::kUniformConstant, set, binding);
+  }
+
+  void AddF32(const std::string& name) {
+    mod()->AddGlobalVariable(std::make_unique<ast::Variable>(
+        name, ast::StorageClass::kUniformConstant, f32_type()));
+  }
+
+  std::unique_ptr<ast::Function> MakeSamplerReferenceBodyFunction(
+      const std::string& func_name,
+      const std::string& texture_name,
+      const std::string& sampler_name,
+      const std::string& coords_name) {
+    std::string result_name = "sampler_result";
+
+    auto body = std::make_unique<ast::BlockStatement>();
+
+    auto call_result = std::make_unique<ast::Variable>(
+        "sampler_result", ast::StorageClass::kFunction, f32_type());
+    body->append(
+        std::make_unique<ast::VariableDeclStatement>(std::move(call_result)));
+
+    ast::ExpressionList call_params;
+    call_params.push_back(
+        std::make_unique<ast::IdentifierExpression>(texture_name));
+    call_params.push_back(
+        std::make_unique<ast::IdentifierExpression>(sampler_name));
+    call_params.push_back(
+        std::make_unique<ast::IdentifierExpression>(coords_name));
+    auto call_expr = std::make_unique<ast::CallExpression>(
+        std::make_unique<ast::IdentifierExpression>("textureSample"),
+        std::move(call_params));
+
+    body->append(std::make_unique<ast::AssignmentStatement>(
+        std::make_unique<ast::IdentifierExpression>("sampler_result"),
+        std::move(call_expr)));
+    body->append(std::make_unique<ast::ReturnStatement>());
+
+    auto func = std::make_unique<ast::Function>(func_name, ast::VariableList(),
+                                                void_type());
+    func->set_body(std::move(body));
+    return func;
+  }
+
   ast::Module* mod() { return &mod_; }
   TypeDeterminer* td() { return td_.get(); }
   Inspector* inspector() { return inspector_.get(); }
@@ -439,6 +526,10 @@
     return array_type_memo_[count].get();
   }
   ast::type::VoidType* void_type() { return &void_type_; }
+  ast::type::SamplerType* sampler_type() { return &sampler_type_; }
+  ast::type::SamplerType* comparison_sampler_type() {
+    return &comparison_sampler_type_;
+  }
 
  private:
   Context ctx_;
@@ -451,6 +542,8 @@
   ast::type::I32Type i32_type_;
   ast::type::U32Type u32_type_;
   ast::type::VoidType void_type_;
+  ast::type::SamplerType sampler_type_;
+  ast::type::SamplerType comparison_sampler_type_;
   std::map<uint32_t, std::unique_ptr<ast::type::ArrayType>> array_type_memo_;
 };
 
@@ -462,10 +555,11 @@
 class InspectorGetStorageBufferResourceBindingsTest : public InspectorTest {};
 class InspectorGetReadOnlyStorageBufferResourceBindingsTest
     : public InspectorTest {};
+class InspectorGetSamplerResourceBindingsTest : public InspectorTest {};
 
 TEST_F(InspectorGetEntryPointTest, NoFunctions) {
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   EXPECT_EQ(0u, result.size());
 }
@@ -474,7 +568,7 @@
   mod()->AddFunction(MakeEmptyBodyFunction("foo"));
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   EXPECT_EQ(0u, result.size());
 }
@@ -486,7 +580,7 @@
   mod()->AddFunction(std::move(foo));
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ("foo", result[0].name);
@@ -505,7 +599,7 @@
   mod()->AddFunction(std::move(bar));
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(2u, result.size());
   EXPECT_EQ("foo", result[0].name);
@@ -545,7 +639,7 @@
   mod()->AddFunction(std::move(foo));
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
   uint32_t x, y, z;
@@ -564,7 +658,7 @@
   mod()->AddFunction(std::move(foo));
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
   uint32_t x, y, z;
@@ -584,7 +678,7 @@
   mod()->AddFunction(std::move(foo));
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
   EXPECT_EQ(0u, result[0].input_variables.size());
@@ -602,7 +696,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -626,7 +720,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -651,7 +745,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -673,7 +767,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -700,7 +794,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(1u, result.size());
 
@@ -728,7 +822,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(2u, result.size());
 
@@ -765,7 +859,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetEntryPoints();
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
 
   ASSERT_EQ(2u, result.size());
 
@@ -933,7 +1027,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   EXPECT_EQ(0u, result.size());
 }
 
@@ -956,7 +1050,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -983,7 +1077,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1035,7 +1129,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(3u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1070,7 +1164,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetUniformBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1097,7 +1191,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1124,7 +1218,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1176,7 +1270,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(3u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1211,7 +1305,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1238,7 +1332,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1265,7 +1359,7 @@
   ASSERT_TRUE(td()->Determine()) << td()->error();
 
   auto result = inspector()->GetStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(0u, result.size());
 }
 
@@ -1289,7 +1383,7 @@
 
   auto result =
       inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1343,7 +1437,7 @@
 
   auto result =
       inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(3u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1379,7 +1473,7 @@
 
   auto result =
       inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1408,7 +1502,7 @@
 
   auto result =
       inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(1u, result.size());
 
   EXPECT_EQ(0u, result[0].bind_group);
@@ -1436,10 +1530,92 @@
 
   auto result =
       inspector()->GetReadOnlyStorageBufferResourceBindings("ep_func");
-  ASSERT_FALSE(inspector()->has_error());
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
   ASSERT_EQ(0u, result.size());
 }
 
+TEST_F(InspectorGetSamplerResourceBindingsTest, Simple) {
+  auto sampled_texture_type =
+      MakeSampledTextureType(ast::type::TextureDimension::k1d, f32_type());
+  AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  AddF32("foo_coords");
+
+  auto func = MakeSamplerReferenceBodyFunction("ep", "foo_texture",
+                                               "foo_sampler", "foo_coords");
+  func->add_decoration(std::make_unique<ast::StageDecoration>(
+      ast::PipelineStage::kVertex, Source{}));
+  mod()->AddFunction(std::move(func));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  auto result = inspector()->GetSamplerResourceBindings("ep");
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(1u, result[0].binding);
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, NoSampler) {
+  auto func = MakeEmptyBodyFunction("ep_func");
+  func->add_decoration(std::make_unique<ast::StageDecoration>(
+      ast::PipelineStage::kVertex, Source{}));
+  mod()->AddFunction(std::move(func));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  auto result = inspector()->GetSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+
+  ASSERT_EQ(0u, result.size());
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, InFunction) {
+  auto sampled_texture_type =
+      MakeSampledTextureType(ast::type::TextureDimension::k1d, f32_type());
+  AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  AddF32("foo_coords");
+
+  auto foo_func = MakeSamplerReferenceBodyFunction("foo_func", "foo_texture",
+                                                   "foo_sampler", "foo_coords");
+  mod()->AddFunction(std::move(foo_func));
+
+  auto ep_func = MakeCallerBodyFunction("ep_func", "foo_func");
+  ep_func->add_decoration(std::make_unique<ast::StageDecoration>(
+      ast::PipelineStage::kVertex, Source{}));
+  mod()->AddFunction(std::move(ep_func));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  auto result = inspector()->GetSamplerResourceBindings("ep_func");
+  ASSERT_FALSE(inspector()->has_error()) << inspector()->error();
+
+  ASSERT_EQ(1u, result.size());
+  EXPECT_EQ(0u, result[0].bind_group);
+  EXPECT_EQ(1u, result[0].binding);
+}
+
+TEST_F(InspectorGetSamplerResourceBindingsTest, UnknownEntryPoint) {
+  auto sampled_texture_type =
+      MakeSampledTextureType(ast::type::TextureDimension::k1d, f32_type());
+  AddSampledTexture("foo_texture", sampled_texture_type.get(), 0, 0);
+  AddSampler("foo_sampler", 0, 1);
+  AddF32("foo_coords");
+
+  auto func = MakeSamplerReferenceBodyFunction("ep", "foo_texture",
+                                               "foo_sampler", "foo_coords");
+  func->add_decoration(std::make_unique<ast::StageDecoration>(
+      ast::PipelineStage::kVertex, Source{}));
+  mod()->AddFunction(std::move(func));
+
+  ASSERT_TRUE(td()->Determine()) << td()->error();
+
+  auto result = inspector()->GetSamplerResourceBindings("foo");
+  ASSERT_TRUE(inspector()->has_error()) << inspector()->error();
+}
+
 }  // namespace
 }  // namespace inspector
 }  // namespace tint