Remove `ast::VariableBindingPoint` in favour of `sem::BindingPoint`.

This CL removes `VariableBindingPoint`. The `Variable` object has a
`has_binding_point` method added which returns true if there is
_both_ a `Group` and `Binding` attribute. Code has all been updated
to use the `sem::BindingPoint` which is populated during the resolve.

Bug: tint:1633
Change-Id: I79a0da662be61d5fb1c1b61342ab239cc4c66809
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/100240
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/ast/variable.cc b/src/tint/ast/variable.cc
index ec87e54..bb719c0 100644
--- a/src/tint/ast/variable.cc
+++ b/src/tint/ast/variable.cc
@@ -37,16 +37,4 @@
 
 Variable::~Variable() = default;
 
-VariableBindingPoint Variable::BindingPoint() const {
-    const GroupAttribute* group = nullptr;
-    const BindingAttribute* binding = nullptr;
-    for (auto* attr : attributes) {
-        Switch(
-            attr,  //
-            [&](const GroupAttribute* a) { group = a; },
-            [&](const BindingAttribute* a) { binding = a; });
-    }
-    return VariableBindingPoint{group, binding};
-}
-
 }  // namespace tint::ast
diff --git a/src/tint/ast/variable.h b/src/tint/ast/variable.h
index 1f5d77a..8772f5b 100644
--- a/src/tint/ast/variable.h
+++ b/src/tint/ast/variable.h
@@ -20,31 +20,19 @@
 
 #include "src/tint/ast/access.h"
 #include "src/tint/ast/attribute.h"
+#include "src/tint/ast/binding_attribute.h"
 #include "src/tint/ast/expression.h"
+#include "src/tint/ast/group_attribute.h"
 #include "src/tint/ast/storage_class.h"
 
 // Forward declarations
 namespace tint::ast {
-class BindingAttribute;
-class GroupAttribute;
 class LocationAttribute;
 class Type;
 }  // namespace tint::ast
 
 namespace tint::ast {
 
-/// VariableBindingPoint holds a group and binding attribute.
-struct VariableBindingPoint {
-    /// The `@group` part of the binding point
-    const GroupAttribute* group = nullptr;
-    /// The `@binding` part of the binding point
-    const BindingAttribute* binding = nullptr;
-
-    /// @returns true if the BindingPoint has a valid group and binding
-    /// attribute.
-    inline operator bool() const { return group && binding; }
-};
-
 /// Variable is the base class for Var, Let, Const, Override and Parameter.
 ///
 /// An instance of this class represents one of five constructs in WGSL: "var"  declaration, "let"
@@ -75,9 +63,11 @@
     /// Destructor
     ~Variable() override;
 
-    /// @returns the binding point information from the variable's attributes.
-    /// @note binding points should only be applied to Var and Parameter types.
-    VariableBindingPoint BindingPoint() const;
+    /// @returns true if the variable has both group and binding attributes
+    bool HasBindingPoint() const {
+        return ast::GetAttribute<ast::BindingAttribute>(attributes) != nullptr &&
+               ast::GetAttribute<ast::GroupAttribute>(attributes) != nullptr;
+    }
 
     /// @returns the kind of the variable, which can be used in diagnostics
     ///          e.g. "var", "let", "const", etc
diff --git a/src/tint/ast/variable_test.cc b/src/tint/ast/variable_test.cc
index 12f528b..14fb766 100644
--- a/src/tint/ast/variable_test.cc
+++ b/src/tint/ast/variable_test.cc
@@ -105,36 +105,24 @@
     EXPECT_EQ(1u, location->value);
 }
 
-TEST_F(VariableTest, BindingPoint) {
+TEST_F(VariableTest, HasBindingPoint_BothProvided) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2), Group(1));
-    EXPECT_TRUE(var->BindingPoint());
-    ASSERT_NE(var->BindingPoint().binding, nullptr);
-    ASSERT_NE(var->BindingPoint().group, nullptr);
-    EXPECT_EQ(var->BindingPoint().binding->value, 2u);
-    EXPECT_EQ(var->BindingPoint().group->value, 1u);
+    EXPECT_TRUE(var->HasBindingPoint());
 }
 
-TEST_F(VariableTest, BindingPointAttributes) {
+TEST_F(VariableTest, HasBindingPoint_NeitherProvided) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, utils::Empty);
-    EXPECT_FALSE(var->BindingPoint());
-    EXPECT_EQ(var->BindingPoint().group, nullptr);
-    EXPECT_EQ(var->BindingPoint().binding, nullptr);
+    EXPECT_FALSE(var->HasBindingPoint());
 }
 
-TEST_F(VariableTest, BindingPointMissingGroupAttribute) {
+TEST_F(VariableTest, HasBindingPoint_MissingGroupAttribute) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Binding(2));
-    EXPECT_FALSE(var->BindingPoint());
-    ASSERT_NE(var->BindingPoint().binding, nullptr);
-    EXPECT_EQ(var->BindingPoint().binding->value, 2u);
-    EXPECT_EQ(var->BindingPoint().group, nullptr);
+    EXPECT_FALSE(var->HasBindingPoint());
 }
 
-TEST_F(VariableTest, BindingPointMissingBindingAttribute) {
+TEST_F(VariableTest, HasBindingPoint_MissingBindingAttribute) {
     auto* var = Var("my_var", ty.i32(), StorageClass::kFunction, Group(1));
-    EXPECT_FALSE(var->BindingPoint());
-    ASSERT_NE(var->BindingPoint().group, nullptr);
-    EXPECT_EQ(var->BindingPoint().group->value, 1u);
-    EXPECT_EQ(var->BindingPoint().binding, nullptr);
+    EXPECT_FALSE(var->HasBindingPoint());
 }
 
 }  // namespace
diff --git a/src/tint/inspector/inspector.cc b/src/tint/inspector/inspector.cc
index 6073470..2f8d09a 100644
--- a/src/tint/inspector/inspector.cc
+++ b/src/tint/inspector/inspector.cc
@@ -370,8 +370,8 @@
 
         ResourceBinding entry;
         entry.resource_type = ResourceBinding::ResourceType::kUniformBuffer;
-        entry.bind_group = binding_info.group->value;
-        entry.binding = binding_info.binding->value;
+        entry.bind_group = binding_info.group;
+        entry.binding = binding_info.binding;
         entry.size = unwrapped_type->Size();
         entry.size_no_padding = entry.size;
         if (auto* str = unwrapped_type->As<sem::Struct>()) {
@@ -410,8 +410,8 @@
 
         ResourceBinding entry;
         entry.resource_type = ResourceBinding::ResourceType::kSampler;
-        entry.bind_group = binding_info.group->value;
-        entry.binding = binding_info.binding->value;
+        entry.bind_group = binding_info.group;
+        entry.binding = binding_info.binding;
 
         result.push_back(entry);
     }
@@ -434,8 +434,8 @@
 
         ResourceBinding entry;
         entry.resource_type = ResourceBinding::ResourceType::kComparisonSampler;
-        entry.bind_group = binding_info.group->value;
-        entry.binding = binding_info.binding->value;
+        entry.bind_group = binding_info.group;
+        entry.binding = binding_info.binding;
 
         result.push_back(entry);
     }
@@ -475,8 +475,8 @@
 
         ResourceBinding entry;
         entry.resource_type = resource_type;
-        entry.bind_group = binding_info.group->value;
-        entry.binding = binding_info.binding->value;
+        entry.bind_group = binding_info.group;
+        entry.binding = binding_info.binding;
 
         auto* tex = var->Type()->UnwrapRef()->As<sem::Texture>();
         entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(tex->dim());
@@ -692,8 +692,8 @@
         ResourceBinding entry;
         entry.resource_type = read_only ? ResourceBinding::ResourceType::kReadOnlyStorageBuffer
                                         : ResourceBinding::ResourceType::kStorageBuffer;
-        entry.bind_group = binding_info.group->value;
-        entry.binding = binding_info.binding->value;
+        entry.bind_group = binding_info.group;
+        entry.binding = binding_info.binding;
         entry.size = unwrapped_type->Size();
         if (auto* str = unwrapped_type->As<sem::Struct>()) {
             entry.size_no_padding = str->SizeNoPadding();
@@ -728,8 +728,8 @@
         entry.resource_type = multisampled_only
                                   ? ResourceBinding::ResourceType::kMultisampledTexture
                                   : ResourceBinding::ResourceType::kSampledTexture;
-        entry.bind_group = binding_info.group->value;
-        entry.binding = binding_info.binding->value;
+        entry.bind_group = binding_info.group;
+        entry.binding = binding_info.binding;
 
         auto* texture_type = var->Type()->UnwrapRef()->As<sem::Texture>();
         entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(texture_type->dim());
@@ -765,8 +765,8 @@
 
         ResourceBinding entry;
         entry.resource_type = ResourceBinding::ResourceType::kWriteOnlyStorageTexture;
-        entry.bind_group = binding_info.group->value;
-        entry.binding = binding_info.binding->value;
+        entry.bind_group = binding_info.group;
+        entry.binding = binding_info.binding;
 
         entry.dim = TypeTextureDimensionToResourceBindingTextureDimension(texture_type->dim());
 
@@ -838,13 +838,8 @@
         GetOriginatingResources(
             std::array<const ast::Expression*, 2>{t, s},
             [&](std::array<const sem::GlobalVariable*, 2> globals) {
-                auto* texture = globals[0]->Declaration()->As<ast::Var>();
-                sem::BindingPoint texture_binding_point = {texture->BindingPoint().group->value,
-                                                           texture->BindingPoint().binding->value};
-
-                auto* sampler = globals[1]->Declaration()->As<ast::Var>();
-                sem::BindingPoint sampler_binding_point = {sampler->BindingPoint().group->value,
-                                                           sampler->BindingPoint().binding->value};
+                auto texture_binding_point = globals[0]->BindingPoint();
+                auto sampler_binding_point = globals[1]->BindingPoint();
 
                 for (auto* entry_point : entry_points) {
                     const auto& ep_name =
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 40156c9..ed07399 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -578,8 +578,21 @@
     sem::Variable* sem = nullptr;
     if (is_global) {
         sem::BindingPoint binding_point;
-        if (auto bp = var->BindingPoint()) {
-            binding_point = {bp.group->value, bp.binding->value};
+        if (var->HasBindingPoint()) {
+            uint32_t binding = 0;
+            {
+                auto* attr = ast::GetAttribute<ast::BindingAttribute>(var->attributes);
+                // TODO(dsinclair): Materialize when binding attribute is an expression
+                binding = attr->value;
+            }
+
+            uint32_t group = 0;
+            {
+                auto* attr = ast::GetAttribute<ast::GroupAttribute>(var->attributes);
+                // TODO(dsinclair): Materialize when group attribute is an expression
+                group = attr->value;
+            }
+            binding_point = {group, binding};
         }
         sem = builder_->create<sem::GlobalVariable>(var, var_ty, sem::EvaluationStage::kRuntime,
                                                     storage_class, access,
@@ -629,8 +642,23 @@
         }
     }
 
+    sem::BindingPoint binding_point;
+    if (param->HasBindingPoint()) {
+        {
+            auto* attr = ast::GetAttribute<ast::BindingAttribute>(param->attributes);
+            // TODO(dsinclair): Materialize the binding information
+            binding_point.binding = attr->value;
+        }
+        {
+            auto* attr = ast::GetAttribute<ast::GroupAttribute>(param->attributes);
+            // TODO(dsinclair): Materialize the group information
+            binding_point.group = attr->value;
+        }
+    }
+
     auto* sem = builder_->create<sem::Parameter>(param, index, ty, ast::StorageClass::kNone,
-                                                 ast::Access::kUndefined);
+                                                 ast::Access::kUndefined,
+                                                 sem::ParameterUsage::kNone, binding_point);
     builder_->Sem().Add(param, sem);
     return sem;
 }
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 6382d34..08451f0 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -664,7 +664,6 @@
         return false;
     }
 
-    auto binding_point = decl->BindingPoint();
     switch (global->StorageClass()) {
         case ast::StorageClass::kUniform:
         case ast::StorageClass::kStorage:
@@ -672,20 +671,23 @@
             // https://gpuweb.github.io/gpuweb/wgsl/#resource-interface
             // Each resource variable must be declared with both group and binding
             // attributes.
-            if (!binding_point) {
+            if (!decl->HasBindingPoint()) {
                 AddError("resource variables require @group and @binding attributes", decl->source);
                 return false;
             }
             break;
         }
-        default:
-            if (binding_point.binding || binding_point.group) {
+        default: {
+            auto* binding_attr = ast::GetAttribute<ast::BindingAttribute>(decl->attributes);
+            auto* group_attr = ast::GetAttribute<ast::GroupAttribute>(decl->attributes);
+            if (binding_attr || group_attr) {
                 // https://gpuweb.github.io/gpuweb/wgsl/#attribute-binding
                 // Must only be applied to a resource variable
                 AddError("non-resource variables must not have @group or @binding attributes",
                          decl->source);
                 return false;
             }
+        }
     }
 
     return true;
@@ -1351,7 +1353,7 @@
     std::unordered_map<sem::BindingPoint, const ast::Variable*> binding_points;
     for (auto* global : func->TransitivelyReferencedGlobals()) {
         auto* var_decl = global->Declaration()->As<ast::Var>();
-        if (!var_decl || !var_decl->BindingPoint()) {
+        if (!var_decl || !var_decl->HasBindingPoint()) {
             continue;
         }
         auto bp = global->BindingPoint();
diff --git a/src/tint/sem/function.cc b/src/tint/sem/function.cc
index ff3a2a7..97171fe 100644
--- a/src/tint/sem/function.cc
+++ b/src/tint/sem/function.cc
@@ -70,8 +70,8 @@
             continue;
         }
 
-        if (auto binding_point = global->Declaration()->BindingPoint()) {
-            ret.push_back({global, binding_point});
+        if (global->Declaration()->HasBindingPoint()) {
+            ret.push_back({global, global->BindingPoint()});
         }
     }
     return ret;
@@ -85,8 +85,8 @@
             continue;
         }
 
-        if (auto binding_point = global->Declaration()->BindingPoint()) {
-            ret.push_back({global, binding_point});
+        if (global->Declaration()->HasBindingPoint()) {
+            ret.push_back({global, global->BindingPoint()});
         }
     }
     return ret;
@@ -129,8 +129,8 @@
     for (auto* global : TransitivelyReferencedGlobals()) {
         auto* unwrapped_type = global->Type()->UnwrapRef();
         if (unwrapped_type->TypeInfo().Is(type)) {
-            if (auto binding_point = global->Declaration()->BindingPoint()) {
-                ret.push_back({global, binding_point});
+            if (global->Declaration()->HasBindingPoint()) {
+                ret.push_back({global, global->BindingPoint()});
             }
         }
     }
@@ -157,8 +157,8 @@
             continue;
         }
 
-        if (auto binding_point = global->Declaration()->BindingPoint()) {
-            ret.push_back({global, binding_point});
+        if (global->Declaration()->HasBindingPoint()) {
+            ret.push_back({global, global->BindingPoint()});
         }
     }
     return ret;
@@ -182,8 +182,8 @@
             continue;
         }
 
-        if (auto binding_point = global->Declaration()->BindingPoint()) {
-            ret.push_back({global, binding_point});
+        if (global->Declaration()->HasBindingPoint()) {
+            ret.push_back({global, global->BindingPoint()});
         }
     }
 
diff --git a/src/tint/sem/function.h b/src/tint/sem/function.h
index a09f81c..d4cb1c7 100644
--- a/src/tint/sem/function.h
+++ b/src/tint/sem/function.h
@@ -54,8 +54,8 @@
 /// Function holds the semantic information for function nodes.
 class Function final : public Castable<Function, CallTarget> {
   public:
-    /// A vector of [Variable*, ast::VariableBindingPoint] pairs
-    using VariableBindings = std::vector<std::pair<const Variable*, ast::VariableBindingPoint>>;
+    /// A vector of [Variable*, sem::BindingPoint] pairs
+    using VariableBindings = std::vector<std::pair<const Variable*, sem::BindingPoint>>;
 
     /// Constructor
     /// @param declaration the ast::Function
diff --git a/src/tint/sem/variable.cc b/src/tint/sem/variable.cc
index 28a373b..f233053 100644
--- a/src/tint/sem/variable.cc
+++ b/src/tint/sem/variable.cc
@@ -72,10 +72,12 @@
                      const sem::Type* type,
                      ast::StorageClass storage_class,
                      ast::Access access,
-                     const ParameterUsage usage /* = ParameterUsage::kNone */)
+                     const ParameterUsage usage /* = ParameterUsage::kNone */,
+                     sem::BindingPoint binding_point /* = {} */)
     : Base(declaration, type, EvaluationStage::kRuntime, storage_class, access, nullptr),
       index_(index),
-      usage_(usage) {}
+      usage_(usage),
+      binding_point_(binding_point) {}
 
 Parameter::~Parameter() = default;
 
diff --git a/src/tint/sem/variable.h b/src/tint/sem/variable.h
index 2bf6d23..b511563 100644
--- a/src/tint/sem/variable.h
+++ b/src/tint/sem/variable.h
@@ -188,12 +188,14 @@
     /// @param storage_class the variable storage class
     /// @param access the variable access control type
     /// @param usage the semantic usage for the parameter
+    /// @param binding_point the optional resource binding point of the parameter
     Parameter(const ast::Parameter* declaration,
               uint32_t index,
               const sem::Type* type,
               ast::StorageClass storage_class,
               ast::Access access,
-              const ParameterUsage usage = ParameterUsage::kNone);
+              const ParameterUsage usage = ParameterUsage::kNone,
+              sem::BindingPoint binding_point = {});
 
     /// Destructor
     ~Parameter() override;
@@ -217,11 +219,15 @@
     /// @param shadows the Type, Function or Variable that this variable shadows
     void SetShadows(const sem::Node* shadows) { shadows_ = shadows; }
 
+    /// @returns the resource binding point for the parameter
+    sem::BindingPoint BindingPoint() const { return binding_point_; }
+
   private:
     const uint32_t index_;
     const ParameterUsage usage_;
     CallTarget const* owner_ = nullptr;
     const sem::Node* shadows_ = nullptr;
+    const sem::BindingPoint binding_point_;
 };
 
 /// VariableUser holds the semantic information for an identifier expression
diff --git a/src/tint/transform/binding_remapper.cc b/src/tint/transform/binding_remapper.cc
index 2807145..eaf9725 100644
--- a/src/tint/transform/binding_remapper.cc
+++ b/src/tint/transform/binding_remapper.cc
@@ -69,8 +69,9 @@
             auto* func = ctx.src->Sem().Get(func_ast);
             std::unordered_map<sem::BindingPoint, int> binding_point_counts;
             for (auto* global : func->TransitivelyReferencedGlobals()) {
-                if (auto binding_point = global->Declaration()->BindingPoint()) {
-                    BindingPoint from{binding_point.group->value, binding_point.binding->value};
+                if (global->Declaration()->HasBindingPoint()) {
+                    BindingPoint from = global->BindingPoint();
+
                     auto bp_it = remappings->binding_points.find(from);
                     if (bp_it != remappings->binding_points.end()) {
                         // Remapped
@@ -90,9 +91,11 @@
     }
 
     for (auto* var : ctx.src->AST().Globals<ast::Var>()) {
-        if (auto binding_point = var->BindingPoint()) {
+        if (var->HasBindingPoint()) {
+            auto* global_sem = ctx.src->Sem().Get<sem::GlobalVariable>(var);
+
             // The original binding point
-            BindingPoint from{binding_point.group->value, binding_point.binding->value};
+            BindingPoint from = global_sem->BindingPoint();
 
             // The binding point after remapping
             BindingPoint bp = from;
@@ -106,8 +109,11 @@
                 auto* new_group = ctx.dst->create<ast::GroupAttribute>(to.group);
                 auto* new_binding = ctx.dst->create<ast::BindingAttribute>(to.binding);
 
-                ctx.Replace(binding_point.group, new_group);
-                ctx.Replace(binding_point.binding, new_binding);
+                auto* old_group = ast::GetAttribute<ast::GroupAttribute>(var->attributes);
+                auto* old_binding = ast::GetAttribute<ast::BindingAttribute>(var->attributes);
+
+                ctx.Replace(old_group, new_group);
+                ctx.Replace(old_binding, new_binding);
                 bp = to;
             }
 
diff --git a/src/tint/transform/combine_samplers.cc b/src/tint/transform/combine_samplers.cc
index 13e6de4..6290b26 100644
--- a/src/tint/transform/combine_samplers.cc
+++ b/src/tint/transform/combine_samplers.cc
@@ -149,12 +149,14 @@
         // Remove all texture and sampler global variables. These will be replaced
         // by combined samplers.
         for (auto* global : ctx.src->AST().GlobalVariables()) {
+            auto* global_sem = sem.Get(global)->As<sem::GlobalVariable>();
             auto* type = sem.Get(global->type);
             if (tint::IsAnyOf<sem::Texture, sem::Sampler>(type) &&
                 !type->Is<sem::StorageTexture>()) {
                 ctx.Remove(ctx.src->AST().GlobalDeclarations(), global);
-            } else if (auto binding_point = global->BindingPoint()) {
-                if (binding_point.group->value == 0 && binding_point.binding->value == 0) {
+            } else if (global->HasBindingPoint()) {
+                auto binding_point = global_sem->BindingPoint();
+                if (binding_point.group == 0 && binding_point.binding == 0) {
                     auto* attribute =
                         ctx.dst->Disable(ast::DisabledValidation::kBindingPointCollision);
                     ctx.InsertFront(global->attributes, attribute);
diff --git a/src/tint/transform/multiplanar_external_texture.cc b/src/tint/transform/multiplanar_external_texture.cc
index d3ac05f..2fb4248 100644
--- a/src/tint/transform/multiplanar_external_texture.cc
+++ b/src/tint/transform/multiplanar_external_texture.cc
@@ -87,7 +87,7 @@
         // represent the secondary plane and one uniform buffer for the
         // ExternalTextureParams struct).
         for (auto* global : ctx.src->AST().GlobalVariables()) {
-            auto* sem_var = sem.Get(global);
+            auto* sem_var = sem.Get<sem::GlobalVariable>(global);
             if (!sem_var->Type()->UnwrapRef()->Is<sem::ExternalTexture>()) {
                 continue;
             }
@@ -109,8 +109,7 @@
             // provided to this transform. We fetch the new binding points by
             // providing the original texture_external binding points into the
             // passed map.
-            BindingPoint bp = {global->BindingPoint().group->value,
-                               global->BindingPoint().binding->value};
+            BindingPoint bp = sem_var->BindingPoint();
 
             BindingsMap::const_iterator it = new_binding_points->bindings_map.find(bp);
             if (it == new_binding_points->bindings_map.end()) {
diff --git a/src/tint/transform/num_workgroups_from_uniform.cc b/src/tint/transform/num_workgroups_from_uniform.cc
index 293c6c4..5772424 100644
--- a/src/tint/transform/num_workgroups_from_uniform.cc
+++ b/src/tint/transform/num_workgroups_from_uniform.cc
@@ -136,9 +136,11 @@
                 group = 0;
 
                 for (auto* global : ctx.src->AST().GlobalVariables()) {
-                    if (auto binding_point = global->BindingPoint()) {
-                        if (binding_point.group->value >= group) {
-                            group = binding_point.group->value + 1;
+                    if (global->HasBindingPoint()) {
+                        auto* global_sem = ctx.src->Sem().Get<sem::GlobalVariable>(global);
+                        auto binding_point = global_sem->BindingPoint();
+                        if (binding_point.group >= group) {
+                            group = binding_point.group + 1;
                         }
                     }
                 }
diff --git a/src/tint/writer/flatten_bindings.cc b/src/tint/writer/flatten_bindings.cc
index 1efc02a..bedec75 100644
--- a/src/tint/writer/flatten_bindings.cc
+++ b/src/tint/writer/flatten_bindings.cc
@@ -33,6 +33,7 @@
     auto entry_points = inspector.GetEntryPoints();
     for (auto& entry_point : entry_points) {
         auto bindings = inspector.GetResourceBindings(entry_point.name);
+
         for (auto& binding : bindings) {
             BindingPoint src = {binding.bind_group, binding.binding};
             if (binding_points.count(src)) {
diff --git a/src/tint/writer/flatten_bindings_test.cc b/src/tint/writer/flatten_bindings_test.cc
index 830e720..7137218 100644
--- a/src/tint/writer/flatten_bindings_test.cc
+++ b/src/tint/writer/flatten_bindings_test.cc
@@ -18,7 +18,6 @@
 
 #include "gtest/gtest.h"
 #include "src/tint/program_builder.h"
-#include "src/tint/resolver/resolver.h"
 #include "src/tint/sem/variable.h"
 
 namespace tint::writer {
@@ -28,9 +27,6 @@
 
 TEST_F(FlattenBindingsTest, NoBindings) {
     ProgramBuilder b;
-
-    resolver::Resolver resolver(&b);
-
     Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
 
@@ -44,8 +40,6 @@
     b.GlobalVar("b", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0), b.Binding(1));
     b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.Group(0), b.Binding(2));
 
-    resolver::Resolver resolver(&b);
-
     Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
 
@@ -60,8 +54,6 @@
     b.GlobalVar("c", b.ty.i32(), ast::StorageClass::kUniform, b.Group(2), b.Binding(2));
     b.WrapInFunction(b.Expr("a"), b.Expr("b"), b.Expr("c"));
 
-    resolver::Resolver resolver(&b);
-
     Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
 
@@ -69,12 +61,21 @@
     EXPECT_TRUE(flattened);
 
     auto& vars = flattened->AST().GlobalVariables();
-    EXPECT_EQ(vars[0]->BindingPoint().group->value, 0u);
-    EXPECT_EQ(vars[0]->BindingPoint().binding->value, 0u);
-    EXPECT_EQ(vars[1]->BindingPoint().group->value, 0u);
-    EXPECT_EQ(vars[1]->BindingPoint().binding->value, 1u);
-    EXPECT_EQ(vars[2]->BindingPoint().group->value, 0u);
-    EXPECT_EQ(vars[2]->BindingPoint().binding->value, 2u);
+
+    auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[0]);
+    ASSERT_NE(sem, nullptr);
+    EXPECT_EQ(sem->BindingPoint().group, 0u);
+    EXPECT_EQ(sem->BindingPoint().binding, 0u);
+
+    sem = flattened->Sem().Get<sem::GlobalVariable>(vars[1]);
+    ASSERT_NE(sem, nullptr);
+    EXPECT_EQ(sem->BindingPoint().group, 0u);
+    EXPECT_EQ(sem->BindingPoint().binding, 1u);
+
+    sem = flattened->Sem().Get<sem::GlobalVariable>(vars[2]);
+    ASSERT_NE(sem, nullptr);
+    EXPECT_EQ(sem->BindingPoint().group, 0u);
+    EXPECT_EQ(sem->BindingPoint().binding, 2u);
 }
 
 TEST_F(FlattenBindingsTest, NotFlat_MultipleNamespaces) {
@@ -113,8 +114,6 @@
                      b.Assign(b.Phony(), "texture4"), b.Assign(b.Phony(), "texture5"),
                      b.Assign(b.Phony(), "texture6"));
 
-    resolver::Resolver resolver(&b);
-
     Program program(std::move(b));
     ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
 
@@ -124,16 +123,22 @@
     auto& vars = flattened->AST().GlobalVariables();
 
     for (size_t i = 0; i < num_buffers; ++i) {
-        EXPECT_EQ(vars[i]->BindingPoint().group->value, 0u);
-        EXPECT_EQ(vars[i]->BindingPoint().binding->value, i);
+        auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[i]);
+        ASSERT_NE(sem, nullptr);
+        EXPECT_EQ(sem->BindingPoint().group, 0u);
+        EXPECT_EQ(sem->BindingPoint().binding, i);
     }
     for (size_t i = 0; i < num_samplers; ++i) {
-        EXPECT_EQ(vars[i + num_buffers]->BindingPoint().group->value, 0u);
-        EXPECT_EQ(vars[i + num_buffers]->BindingPoint().binding->value, i);
+        auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[i + num_buffers]);
+        ASSERT_NE(sem, nullptr);
+        EXPECT_EQ(sem->BindingPoint().group, 0u);
+        EXPECT_EQ(sem->BindingPoint().binding, i);
     }
     for (size_t i = 0; i < num_textures; ++i) {
-        EXPECT_EQ(vars[i + num_buffers + num_samplers]->BindingPoint().group->value, 0u);
-        EXPECT_EQ(vars[i + num_buffers + num_samplers]->BindingPoint().binding->value, i);
+        auto* sem = flattened->Sem().Get<sem::GlobalVariable>(vars[i + num_buffers + num_samplers]);
+        ASSERT_NE(sem, nullptr);
+        EXPECT_EQ(sem->BindingPoint().group, 0u);
+        EXPECT_EQ(sem->BindingPoint().binding, i);
     }
 }
 
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index c2a667f..31f749f 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -1906,10 +1906,10 @@
         TINT_ICE(Writer, builder_.Diagnostics()) << "storage variable must be of struct type";
         return false;
     }
-    ast::VariableBindingPoint bp = var->BindingPoint();
+    auto bp = sem->As<sem::GlobalVariable>()->BindingPoint();
     {
         auto out = line();
-        out << "layout(binding = " << bp.binding->value;
+        out << "layout(binding = " << bp.binding;
         if (version_.IsDesktop()) {
             out << ", std140";
         }
@@ -1930,8 +1930,8 @@
         TINT_ICE(Writer, builder_.Diagnostics()) << "storage variable must be of struct type";
         return false;
     }
-    ast::VariableBindingPoint bp = var->BindingPoint();
-    line() << "layout(binding = " << bp.binding->value << ", std430) buffer "
+    auto bp = sem->As<sem::GlobalVariable>()->BindingPoint();
+    line() << "layout(binding = " << bp.binding << ", std430) buffer "
            << UniqueIdentifier(StructName(str)) << " {";
     EmitStructMembers(current_buffer_, str, /* emit_offsets */ true);
     auto name = builder_.Symbols().NameFor(var->symbol);
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index a7367dc..fca1515 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -136,15 +136,15 @@
 // Helper for writing " : register(RX, spaceY)", where R is the register, X is
 // the binding point binding value, and Y is the binding point group value.
 struct RegisterAndSpace {
-    RegisterAndSpace(char r, ast::VariableBindingPoint bp) : reg(r), binding_point(bp) {}
+    RegisterAndSpace(char r, sem::BindingPoint bp) : reg(r), binding_point(bp) {}
 
     const char reg;
-    ast::VariableBindingPoint const binding_point;
+    sem::BindingPoint const binding_point;
 };
 
 std::ostream& operator<<(std::ostream& s, const RegisterAndSpace& rs) {
-    s << " : register(" << rs.reg << rs.binding_point.binding->value << ", space"
-      << rs.binding_point.group->value << ")";
+    s << " : register(" << rs.reg << rs.binding_point.binding << ", space" << rs.binding_point.group
+      << ")";
     return s;
 }
 
@@ -2861,7 +2861,7 @@
 }
 
 bool GeneratorImpl::EmitUniformVariable(const ast::Var* var, const sem::Variable* sem) {
-    auto binding_point = var->BindingPoint();
+    auto binding_point = sem->As<sem::GlobalVariable>()->BindingPoint();
     auto* type = sem->Type()->UnwrapRef();
     auto name = builder_.Symbols().NameFor(var->symbol);
     line() << "cbuffer cbuffer_" << name << RegisterAndSpace('b', binding_point) << " {";
@@ -2888,7 +2888,9 @@
         return false;
     }
 
-    out << RegisterAndSpace(sem->Access() == ast::Access::kRead ? 't' : 'u', var->BindingPoint())
+    auto* global_sem = sem->As<sem::GlobalVariable>();
+    out << RegisterAndSpace(sem->Access() == ast::Access::kRead ? 't' : 'u',
+                            global_sem->BindingPoint())
         << ";";
 
     return true;
@@ -2916,9 +2918,8 @@
     }
 
     if (register_space) {
-        auto bp = var->BindingPoint();
-        out << " : register(" << register_space << bp.binding->value << ", space" << bp.group->value
-            << ")";
+        auto bp = sem->As<sem::GlobalVariable>()->BindingPoint();
+        out << " : register(" << register_space << bp.binding << ", space" << bp.group << ")";
     }
 
     out << ";";
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 837f9ac..7617678 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -1914,18 +1914,19 @@
     // attribute have a value of zero.
     const uint32_t kInvalidBindingIndex = std::numeric_limits<uint32_t>::max();
     auto get_binding_index = [&](const ast::Parameter* param) -> uint32_t {
-        auto bp = param->BindingPoint();
-        if (bp.group == nullptr || bp.binding == nullptr) {
+        if (!param->HasBindingPoint()) {
             TINT_ICE(Writer, diagnostics_)
                 << "missing binding attributes for entry point parameter";
             return kInvalidBindingIndex;
         }
-        if (bp.group->value != 0) {
+        auto* param_sem = program_->Sem().Get<sem::Parameter>(param);
+        auto bp = param_sem->BindingPoint();
+        if (bp.group != 0) {
             TINT_ICE(Writer, diagnostics_) << "encountered non-zero resource group index (use "
                                               "BindingRemapper to fix)";
             return kInvalidBindingIndex;
         }
-        return bp.binding->value;
+        return bp.binding;
     };
 
     {