Cleanup IOAttributes

Moves binding information into IOAttributes and replaces most of the
SetAttributes calls with direct setters for the members.

Change-Id: Idfef918cd347cfdf732af2784cc3983d835602c7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/240834
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/cmd/bench/BUILD.bazel b/src/tint/cmd/bench/BUILD.bazel
index 816c8a6..052d99a 100644
--- a/src/tint/cmd/bench/BUILD.bazel
+++ b/src/tint/cmd/bench/BUILD.bazel
@@ -92,6 +92,7 @@
     "main_bench.cc",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/type",
diff --git a/src/tint/cmd/bench/BUILD.cmake b/src/tint/cmd/bench/BUILD.cmake
index 4dc16d6..f6e8d5a 100644
--- a/src/tint/cmd/bench/BUILD.cmake
+++ b/src/tint/cmd/bench/BUILD.cmake
@@ -51,6 +51,7 @@
 )
 
 tint_target_add_dependencies(tint_cmd_bench_bench_cmd bench_cmd
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
diff --git a/src/tint/cmd/bench/BUILD.gn b/src/tint/cmd/bench/BUILD.gn
index 35ca535..ec2b0c8 100644
--- a/src/tint/cmd/bench/BUILD.gn
+++ b/src/tint/cmd/bench/BUILD.gn
@@ -100,6 +100,7 @@
       deps = [
         "${dawn_root}/src/utils:utils",
         "${tint_src_dir}:google_benchmark",
+        "${tint_src_dir}/api/common",
         "${tint_src_dir}/lang/core",
         "${tint_src_dir}/lang/core/constant",
         "${tint_src_dir}/lang/core/type",
diff --git a/src/tint/cmd/fuzz/wgsl/BUILD.cmake b/src/tint/cmd/fuzz/wgsl/BUILD.cmake
index eb6a764..68e0961 100644
--- a/src/tint/cmd/fuzz/wgsl/BUILD.cmake
+++ b/src/tint/cmd/fuzz/wgsl/BUILD.cmake
@@ -45,6 +45,7 @@
 )
 
 tint_target_add_dependencies(tint_cmd_fuzz_wgsl_fuzz_cmd fuzz_cmd
+  tint_api_common
   tint_cmd_fuzz_ir_fuzz
   tint_lang_core
   tint_lang_core_constant
diff --git a/src/tint/cmd/fuzz/wgsl/BUILD.gn b/src/tint/cmd/fuzz/wgsl/BUILD.gn
index d117b2c..7db1e90 100644
--- a/src/tint/cmd/fuzz/wgsl/BUILD.gn
+++ b/src/tint/cmd/fuzz/wgsl/BUILD.gn
@@ -85,6 +85,7 @@
     sources = [ "main_fuzz.cc" ]
     deps = [
       "${dawn_root}/src/utils:utils",
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/cmd/fuzz/ir:fuzz",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
diff --git a/src/tint/lang/core/BUILD.bazel b/src/tint/lang/core/BUILD.bazel
index a1ac336..ec5b744 100644
--- a/src/tint/lang/core/BUILD.bazel
+++ b/src/tint/lang/core/BUILD.bazel
@@ -75,6 +75,7 @@
     "unary_op.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/utils",
     "//src/tint/utils/containers",
     "//src/tint/utils/ice",
diff --git a/src/tint/lang/core/BUILD.cmake b/src/tint/lang/core/BUILD.cmake
index 8caa03c..92f1b3d 100644
--- a/src/tint/lang/core/BUILD.cmake
+++ b/src/tint/lang/core/BUILD.cmake
@@ -79,6 +79,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_core lib
+  tint_api_common
   tint_utils
   tint_utils_containers
   tint_utils_ice
diff --git a/src/tint/lang/core/BUILD.gn b/src/tint/lang/core/BUILD.gn
index faa3bf4..6a09ce9 100644
--- a/src/tint/lang/core/BUILD.gn
+++ b/src/tint/lang/core/BUILD.gn
@@ -80,6 +80,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/utils",
     "${tint_src_dir}/utils/containers",
     "${tint_src_dir}/utils/ice",
diff --git a/src/tint/lang/core/constant/BUILD.bazel b/src/tint/lang/core/constant/BUILD.bazel
index 72199af..195e11c 100644
--- a/src/tint/lang/core/constant/BUILD.bazel
+++ b/src/tint/lang/core/constant/BUILD.bazel
@@ -60,6 +60,7 @@
     "value.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/type",
     "//src/tint/utils",
@@ -90,6 +91,7 @@
     "value_test.cc",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/type",
diff --git a/src/tint/lang/core/constant/BUILD.cmake b/src/tint/lang/core/constant/BUILD.cmake
index 963fbcb..58c29d2 100644
--- a/src/tint/lang/core/constant/BUILD.cmake
+++ b/src/tint/lang/core/constant/BUILD.cmake
@@ -59,6 +59,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_core_constant lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_type
   tint_utils
@@ -92,6 +93,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_core_constant_test test
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
diff --git a/src/tint/lang/core/constant/BUILD.gn b/src/tint/lang/core/constant/BUILD.gn
index b982f59..e8532a4 100644
--- a/src/tint/lang/core/constant/BUILD.gn
+++ b/src/tint/lang/core/constant/BUILD.gn
@@ -65,6 +65,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/type",
     "${tint_src_dir}/utils",
@@ -93,6 +94,7 @@
     deps = [
       "${dawn_root}/src/utils:utils",
       "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
       "${tint_src_dir}/lang/core/type",
diff --git a/src/tint/lang/core/intrinsic/BUILD.bazel b/src/tint/lang/core/intrinsic/BUILD.bazel
index 49ca08f..3d877fa6 100644
--- a/src/tint/lang/core/intrinsic/BUILD.bazel
+++ b/src/tint/lang/core/intrinsic/BUILD.bazel
@@ -51,6 +51,7 @@
     "type_matchers.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/type",
@@ -76,6 +77,7 @@
     "table_test.cc",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
diff --git a/src/tint/lang/core/intrinsic/BUILD.cmake b/src/tint/lang/core/intrinsic/BUILD.cmake
index 09d3cd2..4301721 100644
--- a/src/tint/lang/core/intrinsic/BUILD.cmake
+++ b/src/tint/lang/core/intrinsic/BUILD.cmake
@@ -50,6 +50,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_core_intrinsic lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
@@ -78,6 +79,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_core_intrinsic_test test
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_intrinsic
diff --git a/src/tint/lang/core/intrinsic/BUILD.gn b/src/tint/lang/core/intrinsic/BUILD.gn
index 0f5c583..17f38bb 100644
--- a/src/tint/lang/core/intrinsic/BUILD.gn
+++ b/src/tint/lang/core/intrinsic/BUILD.gn
@@ -56,6 +56,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/type",
@@ -77,6 +78,7 @@
     deps = [
       "${dawn_root}/src/utils:utils",
       "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
       "${tint_src_dir}/lang/core/intrinsic",
diff --git a/src/tint/lang/core/io_attributes.h b/src/tint/lang/core/io_attributes.h
index b9e2fe9..4830847 100644
--- a/src/tint/lang/core/io_attributes.h
+++ b/src/tint/lang/core/io_attributes.h
@@ -31,6 +31,7 @@
 #include <cstdint>
 #include <optional>
 
+#include "src/tint/api/common/binding_point.h"
 #include "src/tint/lang/core/builtin_value.h"
 #include "src/tint/lang/core/interpolation.h"
 
@@ -50,6 +51,8 @@
     std::optional<core::Interpolation> interpolation = std::nullopt;
     /// The value of an `@input_attachment_index` attribute
     std::optional<uint32_t> input_attachment_index = std::nullopt;
+    /// The value of the `@binding` and `@group` attributes
+    std::optional<BindingPoint> binding_point = std::nullopt;
     /// True if the object is annotated with `@invariant`.
     bool invariant = false;
 };
diff --git a/src/tint/lang/core/ir/function_param.cc b/src/tint/lang/core/ir/function_param.cc
index 1fb50c64..f73d691 100644
--- a/src/tint/lang/core/ir/function_param.cc
+++ b/src/tint/lang/core/ir/function_param.cc
@@ -42,7 +42,6 @@
 
 FunctionParam* FunctionParam::Clone(CloneContext& ctx) {
     auto* out = ctx.ir.CreateValue<FunctionParam>(type_);
-    out->binding_point_ = binding_point_;
     out->attributes_ = attributes_;
 
     auto name = ctx.ir.NameOf(this);
diff --git a/src/tint/lang/core/ir/function_param.h b/src/tint/lang/core/ir/function_param.h
index 65f4a60..4efdd48 100644
--- a/src/tint/lang/core/ir/function_param.h
+++ b/src/tint/lang/core/ir/function_param.h
@@ -83,6 +83,10 @@
     /// Sets the IO attributes.
     /// @param attrs the attributes
     void SetAttributes(const IOAttributes& attrs) { attributes_ = attrs; }
+
+    /// Resets the function attributes
+    void ResetAttributes() { attributes_ = {}; }
+
     /// @returns the IO attributes
     const IOAttributes& Attributes() const { return attributes_; }
 
@@ -128,22 +132,23 @@
     /// Sets the binding point
     /// @param group the group
     /// @param binding the binding
-    void SetBindingPoint(uint32_t group, uint32_t binding) { binding_point_ = {group, binding}; }
+    void SetBindingPoint(uint32_t group, uint32_t binding) {
+        attributes_.binding_point = {group, binding};
+    }
 
     /// Sets the binding point
     /// @param binding_point the binding point
     void SetBindingPoint(std::optional<struct BindingPoint> binding_point) {
-        binding_point_ = binding_point;
+        attributes_.binding_point = binding_point;
     }
 
     /// @returns the binding points if `Attributes` contains `kBindingPoint`
-    std::optional<struct BindingPoint> BindingPoint() const { return binding_point_; }
+    std::optional<struct BindingPoint> BindingPoint() const { return attributes_.binding_point; }
 
   private:
     ir::Function* func_ = nullptr;
     uint32_t index_ = 0xffffffff;
     const core::type::Type* type_ = nullptr;
-    std::optional<struct BindingPoint> binding_point_;
     IOAttributes attributes_;
 };
 
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
index f56ee58..788c337 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
@@ -538,9 +538,7 @@
     front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
 
     auto* coord = b.FunctionParam("coord", ty.vec2<i32>());
-    IOAttributes coord_attr;
-    coord_attr.location = 0;
-    coord->SetAttributes(coord_attr);
+    coord->SetLocation(0);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing, coord});
     ep->SetReturnLocation(0_u);
diff --git a/src/tint/lang/core/ir/transform/shader_io.cc b/src/tint/lang/core/ir/transform/shader_io.cc
index c73fd65..8c1d25c 100644
--- a/src/tint/lang/core/ir/transform/shader_io.cc
+++ b/src/tint/lang/core/ir/transform/shader_io.cc
@@ -87,7 +87,7 @@
         for (auto* str : structures_to_strip) {
             for (auto* member : str->Members()) {
                 // TODO(crbug.com/tint/745): Remove the const_cast.
-                const_cast<core::type::StructMember*>(member)->SetAttributes({});
+                const_cast<core::type::StructMember*>(member)->ResetAttributes();
             }
         }
     }
@@ -173,7 +173,7 @@
                     // Strip interpolation on non-fragment inputs
                     attributes.interpolation = {};
                 }
-                param->SetAttributes({});
+                param->ResetAttributes();
 
                 auto name = ir.NameOf(param);
                 backend->AddInput(name, param->Type(), std::move(attributes));
diff --git a/src/tint/lang/core/ir/validator_function_test.cc b/src/tint/lang/core/ir/validator_function_test.cc
index b91e1b6..ce53b0c 100644
--- a/src/tint/lang/core/ir/validator_function_test.cc
+++ b/src/tint/lang/core/ir/validator_function_test.cc
@@ -1059,9 +1059,7 @@
     auto* f = ComputeEntryPoint();
 
     auto* v = b.Var(ty.ptr(AddressSpace::kOut, ty.bool_(), core::Access::kReadWrite));
-    IOAttributes attr;
-    attr.location = 0;
-    v->SetAttributes(attr);
+    v->SetLocation(0);
     mod.root_block->Append(v);
 
     b.Append(f->Block(), [&] {
@@ -1084,9 +1082,7 @@
     auto* f = FragmentEntryPoint();
 
     auto* invalid = b.Var("invalid", AddressSpace::kIn, ty.bool_());
-    IOAttributes attr;
-    attr.location = 0;
-    invalid->SetAttributes(attr);
+    invalid->SetLocation(0);
     mod.root_block->Append(invalid);
 
     b.Append(f->Block(), [&] {
diff --git a/src/tint/lang/core/ir/validator_value_test.cc b/src/tint/lang/core/ir/validator_value_test.cc
index 5190be0..70e497b 100644
--- a/src/tint/lang/core/ir/validator_value_test.cc
+++ b/src/tint/lang/core/ir/validator_value_test.cc
@@ -522,10 +522,8 @@
 
 TEST_F(IR_ValidatorTest, Var_MultipleIOAnnotations) {
     auto* v = b.Var<AddressSpace::kIn, vec4<f32>>();
-    IOAttributes attr;
-    attr.builtin = BuiltinValue::kPosition;
-    attr.location = 0;
-    v->SetAttributes(attr);
+    v->SetBuiltin(BuiltinValue::kPosition);
+    v->SetLocation(0);
     mod.root_block->Append(v);
 
     auto res = ir::Validate(mod);
diff --git a/src/tint/lang/core/ir/var.cc b/src/tint/lang/core/ir/var.cc
index ae82645..f6816fa 100644
--- a/src/tint/lang/core/ir/var.cc
+++ b/src/tint/lang/core/ir/var.cc
@@ -55,7 +55,6 @@
     auto* new_result = ctx.Clone(Result());
     auto* new_var = ctx.ir.CreateInstruction<Var>(new_result);
 
-    new_var->binding_point_ = binding_point_;
     new_var->attributes_ = attributes_;
 
     if (auto* init = Initializer()) {
diff --git a/src/tint/lang/core/ir/var.h b/src/tint/lang/core/ir/var.h
index b89f5bf..e361bd0 100644
--- a/src/tint/lang/core/ir/var.h
+++ b/src/tint/lang/core/ir/var.h
@@ -76,9 +76,11 @@
     /// Sets the binding point
     /// @param group the group
     /// @param binding the binding
-    void SetBindingPoint(uint32_t group, uint32_t binding) { binding_point_ = {group, binding}; }
+    void SetBindingPoint(uint32_t group, uint32_t binding) {
+        attributes_.binding_point = {group, binding};
+    }
     /// @returns the binding points if `Attributes` contains `kBindingPoint`
-    std::optional<struct BindingPoint> BindingPoint() const { return binding_point_; }
+    std::optional<struct BindingPoint> BindingPoint() const { return attributes_.binding_point; }
 
     /// Sets the input attachment index
     /// @param index the index
@@ -88,9 +90,42 @@
         return attributes_.input_attachment_index;
     }
 
+    /// Sets the interpolation.
+    /// @param interpolation the optional location interpolation settings
+    void SetInterpolation(std::optional<core::Interpolation> interpolation) {
+        attributes_.interpolation = interpolation;
+    }
+
+    /// Sets the parameter as invariant
+    /// @param val the value to set for invariant
+    void SetInvariant(bool val) { attributes_.invariant = val; }
+
+    /// Sets the blend source.
+    /// @param src the optional value
+    void SetBlendSrc(std::optional<uint32_t> src) { attributes_.blend_src = src; }
+
+    /// Sets the color.
+    /// @param col the optional color value
+    void SetColor(std::optional<uint32_t> col) { attributes_.color = col; }
+
+    /// Sets the location.
+    /// @param loc the optional location value
+    void SetLocation(std::optional<uint32_t> loc) { attributes_.location = loc; }
+
+    /// Sets the builtin information. Note, it is currently an error if the builtin is already set.
+    /// @param val the builtin to set
+    void SetBuiltin(core::BuiltinValue val) {
+        TINT_ASSERT(!attributes_.builtin.has_value());
+        attributes_.builtin = val;
+    }
+
+    /// Resets the IO attributes
+    void ResetAttributes() { attributes_ = {}; }
+
     /// Sets the IO attributes
     /// @param attrs the attributes
     void SetAttributes(const IOAttributes& attrs) { attributes_ = attrs; }
+
     /// @returns the IO attributes
     const IOAttributes& Attributes() const { return attributes_; }
 
@@ -101,7 +136,6 @@
     std::string FriendlyName() const override { return "var"; }
 
   private:
-    std::optional<struct BindingPoint> binding_point_;
     IOAttributes attributes_;
 };
 
diff --git a/src/tint/lang/core/ir/var_test.cc b/src/tint/lang/core/ir/var_test.cc
index cdde494..5be999a 100644
--- a/src/tint/lang/core/ir/var_test.cc
+++ b/src/tint/lang/core/ir/var_test.cc
@@ -74,15 +74,13 @@
     auto* v = b.Var(mod.Types().ptr(core::AddressSpace::kFunction, mod.Types().f32()));
     v->SetInitializer(b.Constant(4_f));
     v->SetBindingPoint(1, 2);
-    v->SetAttributes(IOAttributes{
-        .location = 3,
-        .blend_src = 4,
-        .color = 5,
-        .builtin = core::BuiltinValue::kFragDepth,
-        .interpolation =
-            Interpolation{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid},
-        .invariant = true,
-    });
+    v->SetLocation(3);
+    v->SetBlendSrc(4);
+    v->SetColor(5);
+    v->SetBuiltin(core::BuiltinValue::kFragDepth);
+    v->SetInterpolation(
+        Interpolation{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid});
+    v->SetInvariant(true);
 
     auto* new_v = clone_ctx.Clone(v);
 
diff --git a/src/tint/lang/core/type/BUILD.bazel b/src/tint/lang/core/type/BUILD.bazel
index 7771174..aaa4b1d 100644
--- a/src/tint/lang/core/type/BUILD.bazel
+++ b/src/tint/lang/core/type/BUILD.bazel
@@ -130,6 +130,7 @@
     "void.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/utils",
     "//src/tint/utils/containers",
@@ -183,6 +184,7 @@
     "vector_test.cc",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/type",
     "//src/tint/utils",
diff --git a/src/tint/lang/core/type/BUILD.cmake b/src/tint/lang/core/type/BUILD.cmake
index 5d458a6..56a5484 100644
--- a/src/tint/lang/core/type/BUILD.cmake
+++ b/src/tint/lang/core/type/BUILD.cmake
@@ -129,6 +129,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_core_type lib
+  tint_api_common
   tint_lang_core
   tint_utils
   tint_utils_containers
@@ -185,6 +186,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_core_type_test test
+  tint_api_common
   tint_lang_core
   tint_lang_core_type
   tint_utils
diff --git a/src/tint/lang/core/type/BUILD.gn b/src/tint/lang/core/type/BUILD.gn
index 9737eb1..478e12b 100644
--- a/src/tint/lang/core/type/BUILD.gn
+++ b/src/tint/lang/core/type/BUILD.gn
@@ -135,6 +135,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/utils",
     "${tint_src_dir}/utils/containers",
@@ -186,6 +187,7 @@
     deps = [
       "${dawn_root}/src/utils:utils",
       "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/type",
       "${tint_src_dir}/utils",
diff --git a/src/tint/lang/core/type/struct.h b/src/tint/lang/core/type/struct.h
index f971f92..5146504 100644
--- a/src/tint/lang/core/type/struct.h
+++ b/src/tint/lang/core/type/struct.h
@@ -252,9 +252,18 @@
     /// @returns the optional attributes
     const IOAttributes& Attributes() const { return attributes_; }
 
-    /// Set the attributes of the struct member.
-    /// @param attributes the new attributes
-    void SetAttributes(IOAttributes&& attributes) { attributes_ = std::move(attributes); }
+    /// Sets the interpolation.
+    /// @param interpolation the optional location interpolation settings
+    void SetInterpolation(std::optional<core::Interpolation> interpolation) {
+        attributes_.interpolation = interpolation;
+    }
+
+    /// Sets the location.
+    /// @param loc the optional location value
+    void SetLocation(std::optional<uint32_t> loc) { attributes_.location = loc; }
+
+    /// Resets the attributes to empty
+    void ResetAttributes() { attributes_ = {}; }
 
     /// @param ctx the clone context
     /// @returns a clone of this struct member
diff --git a/src/tint/lang/glsl/intrinsic/BUILD.bazel b/src/tint/lang/glsl/intrinsic/BUILD.bazel
index 7255675..9dbb819 100644
--- a/src/tint/lang/glsl/intrinsic/BUILD.bazel
+++ b/src/tint/lang/glsl/intrinsic/BUILD.bazel
@@ -45,6 +45,7 @@
     "dialect.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
diff --git a/src/tint/lang/glsl/intrinsic/BUILD.cmake b/src/tint/lang/glsl/intrinsic/BUILD.cmake
index 7cb04a3..634ea5a 100644
--- a/src/tint/lang/glsl/intrinsic/BUILD.cmake
+++ b/src/tint/lang/glsl/intrinsic/BUILD.cmake
@@ -44,6 +44,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_glsl_intrinsic lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_intrinsic
diff --git a/src/tint/lang/glsl/intrinsic/BUILD.gn b/src/tint/lang/glsl/intrinsic/BUILD.gn
index ae18937..117ceb0 100644
--- a/src/tint/lang/glsl/intrinsic/BUILD.gn
+++ b/src/tint/lang/glsl/intrinsic/BUILD.gn
@@ -46,6 +46,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/intrinsic",
diff --git a/src/tint/lang/glsl/writer/raise/offset_first_index_test.cc b/src/tint/lang/glsl/writer/raise/offset_first_index_test.cc
index bd0685b..d7a2c5d 100644
--- a/src/tint/lang/glsl/writer/raise/offset_first_index_test.cc
+++ b/src/tint/lang/glsl/writer/raise/offset_first_index_test.cc
@@ -70,16 +70,12 @@
 }
 
 TEST_F(GlslWriter_OffsetFirstIndexTest, NoModify_BuiltinsWithNoOffsets) {
-    core::IOAttributes attributes;
-
     auto* vertex_idx = b.Var("vertex_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kVertexIndex;
-    vertex_idx->SetAttributes(attributes);
+    vertex_idx->SetBuiltin(core::BuiltinValue::kVertexIndex);
     mod.root_block->Append(vertex_idx);
 
     auto* instance_idx = b.Var("instance_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kInstanceIndex;
-    instance_idx->SetAttributes(attributes);
+    instance_idx->SetBuiltin(core::BuiltinValue::kInstanceIndex);
     mod.root_block->Append(instance_idx);
 
     auto* func = b.Function("foo", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex);
@@ -122,16 +118,12 @@
 }
 
 TEST_F(GlslWriter_OffsetFirstIndexTest, VertexOffset) {
-    core::IOAttributes attributes;
-
     auto* vertex_idx = b.Var("vertex_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kVertexIndex;
-    vertex_idx->SetAttributes(attributes);
+    vertex_idx->SetBuiltin(core::BuiltinValue::kVertexIndex);
     mod.root_block->Append(vertex_idx);
 
     auto* instance_idx = b.Var("instance_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kInstanceIndex;
-    instance_idx->SetAttributes(attributes);
+    instance_idx->SetBuiltin(core::BuiltinValue::kInstanceIndex);
     mod.root_block->Append(instance_idx);
 
     auto* func = b.Function("foo", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex);
@@ -199,16 +191,12 @@
 }
 
 TEST_F(GlslWriter_OffsetFirstIndexTest, InstanceOffset) {
-    core::IOAttributes attributes;
-
     auto* vertex_idx = b.Var("vertex_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kVertexIndex;
-    vertex_idx->SetAttributes(attributes);
+    vertex_idx->SetBuiltin(core::BuiltinValue::kVertexIndex);
     mod.root_block->Append(vertex_idx);
 
     auto* instance_idx = b.Var("instance_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kInstanceIndex;
-    instance_idx->SetAttributes(attributes);
+    instance_idx->SetBuiltin(core::BuiltinValue::kInstanceIndex);
     mod.root_block->Append(instance_idx);
 
     auto* func = b.Function("foo", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex);
@@ -276,16 +264,12 @@
 }
 
 TEST_F(GlslWriter_OffsetFirstIndexTest, VertexAndInstanceOffset) {
-    core::IOAttributes attributes;
-
     auto* vertex_idx = b.Var("vertex_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kVertexIndex;
-    vertex_idx->SetAttributes(attributes);
+    vertex_idx->SetBuiltin(core::BuiltinValue::kVertexIndex);
     mod.root_block->Append(vertex_idx);
 
     auto* instance_idx = b.Var("instance_index", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-    attributes.builtin = core::BuiltinValue::kInstanceIndex;
-    instance_idx->SetAttributes(attributes);
+    instance_idx->SetBuiltin(core::BuiltinValue::kInstanceIndex);
     mod.root_block->Append(instance_idx);
 
     auto* func = b.Function("foo", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex);
diff --git a/src/tint/lang/glsl/writer/var_and_let_test.cc b/src/tint/lang/glsl/writer/var_and_let_test.cc
index f44a814..9101d4b 100644
--- a/src/tint/lang/glsl/writer/var_and_let_test.cc
+++ b/src/tint/lang/glsl/writer/var_and_let_test.cc
@@ -120,9 +120,7 @@
 TEST_F(GlslWriterTest, VarInBuiltin) {
     b.Append(b.ir.root_block, [&] {
         auto* v = b.Var("v", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-        core::IOAttributes attrs = {};
-        attrs.builtin = core::BuiltinValue::kLocalInvocationIndex;
-        v->SetAttributes(attrs);
+        v->SetBuiltin(core::BuiltinValue::kLocalInvocationIndex);
     });
 
     ASSERT_TRUE(Generate()) << err_ << output_.glsl;
@@ -136,11 +134,9 @@
 TEST_F(GlslWriterTest, VarIn) {
     b.Append(b.ir.root_block, [&] {
         auto* v = b.Var("v", ty.ptr(core::AddressSpace::kIn, ty.u32()));
-        core::IOAttributes attrs = {};
-        attrs.location = 1;
-        attrs.interpolation = {core::InterpolationType::kFlat,
-                               core::InterpolationSampling::kUndefined};
-        v->SetAttributes(attrs);
+        v->SetLocation(1);
+        v->SetInterpolation(core::Interpolation{core::InterpolationType::kFlat,
+                                                core::InterpolationSampling::kUndefined});
     });
 
     auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -158,10 +154,8 @@
 TEST_F(GlslWriterTest, VarOutBlendSrc) {
     b.Append(b.ir.root_block, [&] {
         auto* v = b.Var("v", ty.ptr(core::AddressSpace::kOut, ty.u32()));
-        core::IOAttributes attrs = {};
-        attrs.location = 1;
-        attrs.blend_src = 1;
-        v->SetAttributes(attrs);
+        v->SetLocation(1);
+        v->SetBlendSrc(1);
     });
 
     auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -182,9 +176,7 @@
 TEST_F(GlslWriterTest, VarOutBuiltin) {
     b.Append(b.ir.root_block, [&] {
         auto* v = b.Var("v", ty.ptr(core::AddressSpace::kOut, ty.u32()));
-        core::IOAttributes attrs = {};
-        attrs.builtin = core::BuiltinValue::kFragDepth;
-        v->SetAttributes(attrs);
+        v->SetBuiltin(core::BuiltinValue::kFragDepth);
     });
 
     auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -202,9 +194,7 @@
 TEST_F(GlslWriterTest, VarBuiltinSampleIndex_ES) {
     b.Append(b.ir.root_block, [&] {
         auto* v = b.Var("v", ty.ptr(core::AddressSpace::kOut, ty.u32()));
-        core::IOAttributes attrs = {};
-        attrs.builtin = core::BuiltinValue::kSampleIndex;
-        v->SetAttributes(attrs);
+        v->SetBuiltin(core::BuiltinValue::kSampleIndex);
     });
 
     auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -223,9 +213,7 @@
 TEST_F(GlslWriterTest, VarBuiltinSampleMask_ES) {
     b.Append(b.ir.root_block, [&] {
         auto* v = b.Var("v", ty.ptr(core::AddressSpace::kOut, ty.u32()));
-        core::IOAttributes attrs = {};
-        attrs.builtin = core::BuiltinValue::kSampleMask;
-        v->SetAttributes(attrs);
+        v->SetBuiltin(core::BuiltinValue::kSampleMask);
     });
 
     auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -244,9 +232,7 @@
 TEST_F(GlslWriterTest, VarBuiltinSampled_NonES) {
     b.Append(b.ir.root_block, [&] {
         auto* v = b.Var("v", ty.ptr(core::AddressSpace::kOut, ty.u32()));
-        core::IOAttributes attrs = {};
-        attrs.builtin = core::BuiltinValue::kSampleIndex;
-        v->SetAttributes(attrs);
+        v->SetBuiltin(core::BuiltinValue::kSampleIndex);
     });
 
     auto* func = b.Function("main", ty.void_(), core::ir::Function::PipelineStage::kFragment);
diff --git a/src/tint/lang/hlsl/intrinsic/BUILD.bazel b/src/tint/lang/hlsl/intrinsic/BUILD.bazel
index 649b2b0..b8db0bf 100644
--- a/src/tint/lang/hlsl/intrinsic/BUILD.bazel
+++ b/src/tint/lang/hlsl/intrinsic/BUILD.bazel
@@ -46,6 +46,7 @@
     "type_matchers.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
diff --git a/src/tint/lang/hlsl/intrinsic/BUILD.cmake b/src/tint/lang/hlsl/intrinsic/BUILD.cmake
index 428f94a..f5828b7 100644
--- a/src/tint/lang/hlsl/intrinsic/BUILD.cmake
+++ b/src/tint/lang/hlsl/intrinsic/BUILD.cmake
@@ -45,6 +45,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_hlsl_intrinsic lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_intrinsic
diff --git a/src/tint/lang/hlsl/intrinsic/BUILD.gn b/src/tint/lang/hlsl/intrinsic/BUILD.gn
index 0a199f3..4f6e370 100644
--- a/src/tint/lang/hlsl/intrinsic/BUILD.gn
+++ b/src/tint/lang/hlsl/intrinsic/BUILD.gn
@@ -47,6 +47,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/intrinsic",
diff --git a/src/tint/lang/hlsl/type/BUILD.bazel b/src/tint/lang/hlsl/type/BUILD.bazel
index fb3d6d7..476fa94 100644
--- a/src/tint/lang/hlsl/type/BUILD.bazel
+++ b/src/tint/lang/hlsl/type/BUILD.bazel
@@ -51,6 +51,7 @@
     "uint8_t4_packed.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/type",
     "//src/tint/utils",
diff --git a/src/tint/lang/hlsl/type/BUILD.cmake b/src/tint/lang/hlsl/type/BUILD.cmake
index c50de6a..fdf81ee 100644
--- a/src/tint/lang/hlsl/type/BUILD.cmake
+++ b/src/tint/lang/hlsl/type/BUILD.cmake
@@ -50,6 +50,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_hlsl_type lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_type
   tint_utils
diff --git a/src/tint/lang/hlsl/type/BUILD.gn b/src/tint/lang/hlsl/type/BUILD.gn
index 3a7d315..977c9e0 100644
--- a/src/tint/lang/hlsl/type/BUILD.gn
+++ b/src/tint/lang/hlsl/type/BUILD.gn
@@ -56,6 +56,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/type",
     "${tint_src_dir}/utils",
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
index 7ee4cdd..676e0c1 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
@@ -1386,10 +1386,8 @@
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    core::IOAttributes index_attr;
-    index_attr.location = 0;
     auto index = b.FunctionParam(ty.u32());
-    index->SetAttributes(index_attr);
+    index->SetLocation(0);
     func->SetParams({index});
     b.Append(func->Block(), [&] {
         auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
@@ -1579,10 +1577,8 @@
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    core::IOAttributes index_attr;
-    index_attr.location = 0;
     auto index = b.FunctionParam(ty.u32());
-    index->SetAttributes(index_attr);
+    index->SetLocation(0);
     func->SetParams({index});
     b.Append(func->Block(), [&] {
         auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
@@ -1779,10 +1775,8 @@
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    core::IOAttributes index_attr;
-    index_attr.location = 0;
     auto index = b.FunctionParam(ty.u32());
-    index->SetAttributes(index_attr);
+    index->SetLocation(0);
     func->SetParams({index});
     b.Append(func->Block(), [&] {
         auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
@@ -1994,10 +1988,8 @@
     b.ir.root_block->Append(var);
 
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
-    core::IOAttributes index_attr;
-    index_attr.location = 0;
     auto index = b.FunctionParam(ty.u32());
-    index->SetAttributes(index_attr);
+    index->SetLocation(0);
     func->SetParams({index});
     b.Append(func->Block(), [&] {
         auto* access = b.Access(ty.ptr<storage>(ty.atomic<i32>()), var, 0_u, index, 1_u, index);
diff --git a/src/tint/lang/msl/intrinsic/BUILD.bazel b/src/tint/lang/msl/intrinsic/BUILD.bazel
index 3ea1437..123479d 100644
--- a/src/tint/lang/msl/intrinsic/BUILD.bazel
+++ b/src/tint/lang/msl/intrinsic/BUILD.bazel
@@ -46,6 +46,7 @@
     "type_matchers.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
diff --git a/src/tint/lang/msl/intrinsic/BUILD.cmake b/src/tint/lang/msl/intrinsic/BUILD.cmake
index c8fced1..a79aa26 100644
--- a/src/tint/lang/msl/intrinsic/BUILD.cmake
+++ b/src/tint/lang/msl/intrinsic/BUILD.cmake
@@ -45,6 +45,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_msl_intrinsic lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_intrinsic
diff --git a/src/tint/lang/msl/intrinsic/BUILD.gn b/src/tint/lang/msl/intrinsic/BUILD.gn
index ed6c99f..a688f88 100644
--- a/src/tint/lang/msl/intrinsic/BUILD.gn
+++ b/src/tint/lang/msl/intrinsic/BUILD.gn
@@ -47,6 +47,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/intrinsic",
diff --git a/src/tint/lang/msl/type/BUILD.bazel b/src/tint/lang/msl/type/BUILD.bazel
index ba69590..76d1514 100644
--- a/src/tint/lang/msl/type/BUILD.bazel
+++ b/src/tint/lang/msl/type/BUILD.bazel
@@ -49,6 +49,7 @@
     "level.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/type",
     "//src/tint/utils",
diff --git a/src/tint/lang/msl/type/BUILD.cmake b/src/tint/lang/msl/type/BUILD.cmake
index bd94679..7e10393 100644
--- a/src/tint/lang/msl/type/BUILD.cmake
+++ b/src/tint/lang/msl/type/BUILD.cmake
@@ -48,6 +48,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_msl_type lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_type
   tint_utils
diff --git a/src/tint/lang/msl/type/BUILD.gn b/src/tint/lang/msl/type/BUILD.gn
index fa101f9..7aa306f 100644
--- a/src/tint/lang/msl/type/BUILD.gn
+++ b/src/tint/lang/msl/type/BUILD.gn
@@ -54,6 +54,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/type",
     "${tint_src_dir}/utils",
diff --git a/src/tint/lang/msl/writer/raise/simd_ballot_test.cc b/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
index baa2f1b..848dd7b 100644
--- a/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
+++ b/src/tint/lang/msl/writer/raise/simd_ballot_test.cc
@@ -44,9 +44,7 @@
 TEST_F(MslWriter_SimdBallotTest, SimdBallot_WithUserDeclaredSubgroupSize) {
     auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
     auto* subgroup_size = b.FunctionParam("user_subgroup_size", ty.u32());
-    core::IOAttributes attr;
-    attr.location = 0;
-    subgroup_size->SetAttributes(attr);
+    subgroup_size->SetLocation(0);
     func->SetParams({subgroup_size});
     b.Append(func->Block(), [&] {  //
         b.Call<vec4<u32>>(core::BuiltinFn::kSubgroupBallot, true);
@@ -163,9 +161,7 @@
 
     auto* ep1 = b.Function("ep1", ty.void_(), core::ir::Function::PipelineStage::kFragment);
     auto* subgroup_size = b.FunctionParam("user_subgroup_size", ty.u32());
-    core::IOAttributes attr;
-    attr.location = 0;
-    subgroup_size->SetAttributes(attr);
+    subgroup_size->SetLocation(0);
     ep1->SetParams({subgroup_size});
     b.Append(ep1->Block(), [&] {  //
         b.Call<vec4<u32>>(foo, true);
diff --git a/src/tint/lang/spirv/intrinsic/BUILD.bazel b/src/tint/lang/spirv/intrinsic/BUILD.bazel
index 18e6b09..bc0685a 100644
--- a/src/tint/lang/spirv/intrinsic/BUILD.bazel
+++ b/src/tint/lang/spirv/intrinsic/BUILD.bazel
@@ -46,6 +46,7 @@
     "type_matchers.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
diff --git a/src/tint/lang/spirv/intrinsic/BUILD.cmake b/src/tint/lang/spirv/intrinsic/BUILD.cmake
index 6d9b838..c234134 100644
--- a/src/tint/lang/spirv/intrinsic/BUILD.cmake
+++ b/src/tint/lang/spirv/intrinsic/BUILD.cmake
@@ -45,6 +45,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_spirv_intrinsic lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_intrinsic
diff --git a/src/tint/lang/spirv/intrinsic/BUILD.gn b/src/tint/lang/spirv/intrinsic/BUILD.gn
index 90cf896..ca442d8 100644
--- a/src/tint/lang/spirv/intrinsic/BUILD.gn
+++ b/src/tint/lang/spirv/intrinsic/BUILD.gn
@@ -47,6 +47,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/intrinsic",
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.cc b/src/tint/lang/spirv/reader/lower/shader_io.cc
index fba910a..272d800 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io.cc
@@ -96,11 +96,11 @@
         // Remove attributes from all of the original structs and module-scope output variables.
         // This is done last as we need to copy attributes during `ProcessEntryPointOutputs()`.
         for (auto& var : output_variables) {
-            var->SetAttributes({});
+            var->ResetAttributes();
             if (auto* str = var->Result()->Type()->UnwrapPtr()->As<core::type::Struct>()) {
                 for (auto* member : str->Members()) {
                     // TODO(crbug.com/tint/745): Remove the const_cast.
-                    const_cast<core::type::StructMember*>(member)->SetAttributes({});
+                    const_cast<core::type::StructMember*>(member)->ResetAttributes();
                 }
             }
         }
@@ -440,19 +440,19 @@
     void AddEntryPointParameterAttributes(core::ir::FunctionParam* param,
                                           const core::IOAttributes& attributes) {
         if (auto* str = param->Type()->UnwrapPtr()->As<core::type::Struct>()) {
-            for (auto* member : str->Members()) {
+            for (const auto* const_member : str->Members()) {
+                // TODO(crbug.com/tint/745): Remove the const_cast.
+                auto* member = const_cast<core::type::StructMember*>(const_member);
+
                 // Use the base variable attributes if not specified directly on the member.
                 auto member_attributes = member->Attributes();
                 if (auto base_loc = attributes.location) {
                     // Location values increment from the base location value on the variable.
-                    member_attributes.location = base_loc.value() + member->Index();
+                    member->SetLocation(base_loc.value() + member->Index());
                 }
                 if (!member_attributes.interpolation) {
-                    member_attributes.interpolation = attributes.interpolation;
+                    member->SetInterpolation(attributes.interpolation);
                 }
-                // TODO(crbug.com/tint/745): Remove the const_cast.
-                const_cast<core::type::StructMember*>(member)->SetAttributes(
-                    std::move(member_attributes));
             }
         } else {
             // Set attributes directly on the function parameter.
diff --git a/src/tint/lang/spirv/reader/lower/shader_io_test.cc b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
index 02c4263..a427870 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io_test.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
@@ -79,32 +79,20 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Inputs) {
     auto* front_facing = b.Var("front_facing", ty.ptr(core::AddressSpace::kIn, ty.bool_()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kFrontFacing;
-        front_facing->SetAttributes(std::move(attributes));
-    }
+    front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing);
+
     auto* position = b.Var("position", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kPosition;
-        attributes.invariant = true;
-        position->SetAttributes(std::move(attributes));
-    }
+    position->SetBuiltin(core::BuiltinValue::kPosition);
+    position->SetInvariant(true);
+
     auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kIn, ty.f32()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 0;
-        color1->SetAttributes(std::move(attributes));
-    }
+    color1->SetLocation(0);
+
     auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kIn, ty.f32()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1;
-        attributes.interpolation = core::Interpolation{core::InterpolationType::kLinear,
-                                                       core::InterpolationSampling::kSample};
-        color2->SetAttributes(std::move(attributes));
-    }
+    color2->SetLocation(1);
+    color2->SetInterpolation(core::Interpolation{core::InterpolationType::kLinear,
+                                                 core::InterpolationSampling::kSample});
+
     mod.root_block->Append(front_facing);
     mod.root_block->Append(position);
     mod.root_block->Append(color1);
@@ -172,32 +160,20 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedByHelper) {
     auto* front_facing = b.Var("front_facing", ty.ptr(core::AddressSpace::kIn, ty.bool_()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kFrontFacing;
-        front_facing->SetAttributes(std::move(attributes));
-    }
+    front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing);
+
     auto* position = b.Var("position", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kPosition;
-        attributes.invariant = true;
-        position->SetAttributes(std::move(attributes));
-    }
+    position->SetBuiltin(core::BuiltinValue::kPosition);
+    position->SetInvariant(true);
+
     auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kIn, ty.f32()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 0;
-        color1->SetAttributes(std::move(attributes));
-    }
+    color1->SetLocation(0);
+
     auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kIn, ty.f32()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1;
-        attributes.interpolation = core::Interpolation{core::InterpolationType::kLinear,
-                                                       core::InterpolationSampling::kSample};
-        color2->SetAttributes(std::move(attributes));
-    }
+    color2->SetLocation(1);
+    color2->SetInterpolation(core::Interpolation{core::InterpolationType::kLinear,
+                                                 core::InterpolationSampling::kSample});
+
     mod.root_block->Append(front_facing);
     mod.root_block->Append(position);
     mod.root_block->Append(color1);
@@ -309,23 +285,14 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedEntryPointAndHelper) {
     auto* gid = b.Var("gid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kGlobalInvocationId;
-        gid->SetAttributes(std::move(attributes));
-    }
+    gid->SetBuiltin(core::BuiltinValue::kGlobalInvocationId);
+
     auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kLocalInvocationId;
-        lid->SetAttributes(std::move(attributes));
-    }
+    lid->SetBuiltin(core::BuiltinValue::kLocalInvocationId);
+
     auto* group_id = b.Var("group_id", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kWorkgroupId;
-        group_id->SetAttributes(std::move(attributes));
-    }
+    group_id->SetBuiltin(core::BuiltinValue::kWorkgroupId);
+
     mod.root_block->Append(gid);
     mod.root_block->Append(lid);
     mod.root_block->Append(group_id);
@@ -399,23 +366,14 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedEntryPointAndHelper_ForwardReference) {
     auto* gid = b.Var("gid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kGlobalInvocationId;
-        gid->SetAttributes(std::move(attributes));
-    }
+    gid->SetBuiltin(core::BuiltinValue::kGlobalInvocationId);
+
     auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kLocalInvocationId;
-        lid->SetAttributes(std::move(attributes));
-    }
+    lid->SetBuiltin(core::BuiltinValue::kLocalInvocationId);
+
     auto* group_id = b.Var("group_id", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kWorkgroupId;
-        group_id->SetAttributes(std::move(attributes));
-    }
+    group_id->SetBuiltin(core::BuiltinValue::kWorkgroupId);
+
     mod.root_block->Append(gid);
     mod.root_block->Append(lid);
     mod.root_block->Append(group_id);
@@ -490,23 +448,14 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Inputs_UsedByMultipleEntryPoints) {
     auto* gid = b.Var("gid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kGlobalInvocationId;
-        gid->SetAttributes(std::move(attributes));
-    }
+    gid->SetBuiltin(core::BuiltinValue::kGlobalInvocationId);
+
     auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kLocalInvocationId;
-        lid->SetAttributes(std::move(attributes));
-    }
+    lid->SetBuiltin(core::BuiltinValue::kLocalInvocationId);
+
     auto* group_id = b.Var("group_id", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kWorkgroupId;
-        group_id->SetAttributes(std::move(attributes));
-    }
+    group_id->SetBuiltin(core::BuiltinValue::kWorkgroupId);
+
     mod.root_block->Append(gid);
     mod.root_block->Append(lid);
     mod.root_block->Append(group_id);
@@ -597,11 +546,8 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Input_LoadVectorElement) {
     auto* lid = b.Var("lid", ty.ptr(core::AddressSpace::kIn, ty.vec3<u32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kLocalInvocationId;
-        lid->SetAttributes(std::move(attributes));
-    }
+    lid->SetBuiltin(core::BuiltinValue::kLocalInvocationId);
+
     mod.root_block->Append(lid);
 
     auto* ep = b.ComputeFunction("foo");
@@ -747,11 +693,8 @@
             },
         });
     auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kIn, colors_str));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        colors->SetAttributes(attributes);
-    }
+    colors->SetLocation(1u);
+
     mod.root_block->Append(colors);
 
     auto* foo = b.Function("foo", ty.void_());
@@ -843,12 +786,9 @@
             },
         });
     auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kIn, colors_str));
-    {
-        core::IOAttributes attributes;
-        attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective,
-                                                       core::InterpolationSampling::kCentroid};
-        colors->SetAttributes(attributes);
-    }
+    colors->SetInterpolation(core::Interpolation{core::InterpolationType::kPerspective,
+                                                 core::InterpolationSampling::kCentroid});
+
     mod.root_block->Append(colors);
 
     auto* foo = b.Function("foo", ty.void_());
@@ -1014,11 +954,8 @@
 
 TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Builtin) {
     auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kPosition;
-        position->SetAttributes(std::move(attributes));
-    }
+    position->SetBuiltin(core::BuiltinValue::kPosition);
+
     mod.root_block->Append(position);
 
     auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex);
@@ -1068,12 +1005,9 @@
 
 TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Builtin_WithInvariant) {
     auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kPosition;
-        attributes.invariant = true;
-        position->SetAttributes(std::move(attributes));
-    }
+    position->SetBuiltin(core::BuiltinValue::kPosition);
+    position->SetInvariant(true);
+
     mod.root_block->Append(position);
 
     auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kVertex);
@@ -1123,11 +1057,8 @@
 
 TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Location) {
     auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        color->SetAttributes(std::move(attributes));
-    }
+    color->SetLocation(1u);
+
     mod.root_block->Append(color);
 
     auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -1177,13 +1108,10 @@
 
 TEST_F(SpirvReader_ShaderIOTest, SingleOutput_Location_WithInterpolation) {
     auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective,
-                                                       core::InterpolationSampling::kCentroid};
-        color->SetAttributes(std::move(attributes));
-    }
+    color->SetLocation(1u);
+    color->SetInterpolation(core::Interpolation{core::InterpolationType::kPerspective,
+                                                core::InterpolationSampling::kCentroid});
+
     mod.root_block->Append(color);
 
     auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -1233,26 +1161,17 @@
 
 TEST_F(SpirvReader_ShaderIOTest, MultipleOutputs) {
     auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kPosition;
-        attributes.invariant = true;
-        position->SetAttributes(std::move(attributes));
-    }
+    position->SetBuiltin(core::BuiltinValue::kPosition);
+    position->SetInvariant(true);
+
     auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        color1->SetAttributes(std::move(attributes));
-    }
+    color1->SetLocation(1u);
+
     auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective,
-                                                       core::InterpolationSampling::kCentroid};
-        color2->SetAttributes(std::move(attributes));
-    }
+    color2->SetLocation(1u);
+    color2->SetInterpolation(core::Interpolation{core::InterpolationType::kPerspective,
+                                                 core::InterpolationSampling::kCentroid});
+
     mod.root_block->Append(position);
     mod.root_block->Append(color1);
     mod.root_block->Append(color2);
@@ -1469,11 +1388,8 @@
 
     auto* builtins = b.Var("builtins", ty.ptr(core::AddressSpace::kOut, builtin_str));
     auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kOut, colors_str));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        colors->SetAttributes(attributes);
-    }
+    colors->SetLocation(1u);
+
     mod.root_block->Append(builtins);
     mod.root_block->Append(colors);
 
@@ -1593,12 +1509,9 @@
 
     auto* builtins = b.Var("builtins", ty.ptr(core::AddressSpace::kOut, builtin_str));
     auto* colors = b.Var("colors", ty.ptr(core::AddressSpace::kOut, colors_str));
-    {
-        core::IOAttributes attributes;
-        attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective,
-                                                       core::InterpolationSampling::kCentroid};
-        colors->SetAttributes(attributes);
-    }
+    colors->SetInterpolation(core::Interpolation{core::InterpolationType::kPerspective,
+                                                 core::InterpolationSampling::kCentroid});
+
     mod.root_block->Append(builtins);
     mod.root_block->Append(colors);
 
@@ -1694,26 +1607,17 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Outputs_UsedByMultipleEntryPoints) {
     auto* position = b.Var("position", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kPosition;
-        attributes.invariant = true;
-        position->SetAttributes(std::move(attributes));
-    }
+    position->SetBuiltin(core::BuiltinValue::kPosition);
+    position->SetInvariant(true);
+
     auto* color1 = b.Var("color1", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        color1->SetAttributes(std::move(attributes));
-    }
+    color1->SetLocation(1u);
+
     auto* color2 = b.Var("color2", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        attributes.interpolation = core::Interpolation{core::InterpolationType::kPerspective,
-                                                       core::InterpolationSampling::kCentroid};
-        color2->SetAttributes(std::move(attributes));
-    }
+    color2->SetLocation(1u);
+    color2->SetInterpolation(core::Interpolation{core::InterpolationType::kPerspective,
+                                                 core::InterpolationSampling::kCentroid});
+
     mod.root_block->Append(position);
     mod.root_block->Append(color1);
     mod.root_block->Append(color2);
@@ -1839,11 +1743,8 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Output_LoadAndStore) {
     auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        color->SetAttributes(std::move(attributes));
-    }
+    color->SetLocation(1u);
+
     mod.root_block->Append(color);
 
     auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -1902,11 +1803,8 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Output_LoadVectorElementAndStoreVectorElement) {
     auto* color = b.Var("color", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1u;
-        color->SetAttributes(std::move(attributes));
-    }
+    color->SetLocation(1u);
+
     mod.root_block->Append(color);
 
     auto* ep = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
@@ -1965,30 +1863,18 @@
 
 TEST_F(SpirvReader_ShaderIOTest, Inputs_And_Outputs) {
     auto* position = b.Var("position", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kPosition;
-        attributes.invariant = true;
-        position->SetAttributes(std::move(attributes));
-    }
+    position->SetBuiltin(core::BuiltinValue::kPosition);
+    position->SetInvariant(true);
+
     auto* color_in = b.Var("color_in", ty.ptr(core::AddressSpace::kIn, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 0;
-        color_in->SetAttributes(std::move(attributes));
-    }
+    color_in->SetLocation(0);
+
     auto* color_out_1 = b.Var("color_out_1", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 1;
-        color_out_1->SetAttributes(std::move(attributes));
-    }
+    color_out_1->SetLocation(1);
+
     auto* color_out_2 = b.Var("color_out_2", ty.ptr(core::AddressSpace::kOut, ty.vec4<f32>()));
-    {
-        core::IOAttributes attributes;
-        attributes.location = 2;
-        color_out_2->SetAttributes(std::move(attributes));
-    }
+    color_out_2->SetLocation(2);
+
     mod.root_block->Append(position);
     mod.root_block->Append(color_in);
     mod.root_block->Append(color_out_1);
@@ -2061,17 +1947,11 @@
 TEST_F(SpirvReader_ShaderIOTest, SampleMask) {
     auto* arr = ty.array<u32, 1>();
     auto* mask_in = b.Var("mask_in", ty.ptr(core::AddressSpace::kIn, arr));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kSampleMask;
-        mask_in->SetAttributes(std::move(attributes));
-    }
+    mask_in->SetBuiltin(core::BuiltinValue::kSampleMask);
+
     auto* mask_out = b.Var("mask_out", ty.ptr(core::AddressSpace::kOut, arr));
-    {
-        core::IOAttributes attributes;
-        attributes.builtin = core::BuiltinValue::kSampleMask;
-        mask_out->SetAttributes(std::move(attributes));
-    }
+    mask_out->SetBuiltin(core::BuiltinValue::kSampleMask);
+
     mod.root_block->Append(mask_in);
     mod.root_block->Append(mask_out);
 
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index f6cb6af..8b3cde0 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -2464,7 +2464,7 @@
 
         if (group || binding) {
             TINT_ASSERT(group && binding);
-            var->SetBindingPoint(group.value(), binding.value());
+            io_attributes.binding_point = {group.value(), binding.value()};
         }
         var->SetAttributes(std::move(io_attributes));
 
diff --git a/src/tint/lang/spirv/type/BUILD.bazel b/src/tint/lang/spirv/type/BUILD.bazel
index e7e650f..63887de 100644
--- a/src/tint/lang/spirv/type/BUILD.bazel
+++ b/src/tint/lang/spirv/type/BUILD.bazel
@@ -49,6 +49,7 @@
     "sampled_image.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/ir",
@@ -76,6 +77,7 @@
     "sampled_image_test.cc",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/ir",
diff --git a/src/tint/lang/spirv/type/BUILD.cmake b/src/tint/lang/spirv/type/BUILD.cmake
index bdec23d..d37563c 100644
--- a/src/tint/lang/spirv/type/BUILD.cmake
+++ b/src/tint/lang/spirv/type/BUILD.cmake
@@ -48,6 +48,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_spirv_type lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_ir
@@ -78,6 +79,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_spirv_type_test test
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_ir
diff --git a/src/tint/lang/spirv/type/BUILD.gn b/src/tint/lang/spirv/type/BUILD.gn
index 0e75b40..a8a709d 100644
--- a/src/tint/lang/spirv/type/BUILD.gn
+++ b/src/tint/lang/spirv/type/BUILD.gn
@@ -54,6 +54,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/ir",
@@ -79,6 +80,7 @@
     deps = [
       "${dawn_root}/src/utils:utils",
       "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
       "${tint_src_dir}/lang/core/ir",
diff --git a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types_test.cc b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types_test.cc
index d640d3a..80ae06c 100644
--- a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types_test.cc
+++ b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types_test.cc
@@ -392,16 +392,14 @@
 }
 
 TEST_F(SpirvWriter_ForkExplicitLayoutTypesTest, Storage_SharedWithInOut) {
-    core::IOAttributes attributes;
-    attributes.builtin = core::BuiltinValue::kSampleMask;
     auto* array = ty.array<u32, 1>();
     b.Append(mod.root_block, [&] {
         auto* buffer = b.Var("buffer", ty.ptr(storage, array));
         buffer->SetBindingPoint(0, 0);
         auto* mask_in = b.Var("mask_in", ty.ptr(core::AddressSpace::kIn, array));
-        mask_in->SetAttributes(attributes);
+        mask_in->SetBuiltin(core::BuiltinValue::kSampleMask);
         auto* mask_out = b.Var("mask_out", ty.ptr(core::AddressSpace::kOut, array));
-        mask_out->SetAttributes(attributes);
+        mask_out->SetBuiltin(core::BuiltinValue::kSampleMask);
     });
 
     auto* src = R"(
diff --git a/src/tint/lang/spirv/writer/raise/merge_return_test.cc b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
index 35159ce..616813e 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return_test.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
@@ -213,9 +213,7 @@
 
 TEST_F(SpirvWriter_MergeReturnTest, NoModify_EntryPoint_IfElse_OneSideReturns) {
     auto* cond = b.FunctionParam(ty.u32());
-    core::IOAttributes attr;
-    attr.location = 0;
-    cond->SetAttributes(attr);
+    cond->SetLocation(0);
     auto* func = b.ComputeFunction("entrypointfunction", 2_u, 3_u, 4_u);
     func->SetParams({cond});
     b.Append(func->Block(), [&] {
diff --git a/src/tint/lang/wgsl/intrinsic/BUILD.bazel b/src/tint/lang/wgsl/intrinsic/BUILD.bazel
index 15f5d4a..949d03b 100644
--- a/src/tint/lang/wgsl/intrinsic/BUILD.bazel
+++ b/src/tint/lang/wgsl/intrinsic/BUILD.bazel
@@ -48,6 +48,7 @@
     "type_matchers.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/intrinsic",
diff --git a/src/tint/lang/wgsl/intrinsic/BUILD.cmake b/src/tint/lang/wgsl/intrinsic/BUILD.cmake
index ac7f047..d175593 100644
--- a/src/tint/lang/wgsl/intrinsic/BUILD.cmake
+++ b/src/tint/lang/wgsl/intrinsic/BUILD.cmake
@@ -47,6 +47,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_wgsl_intrinsic lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_intrinsic
diff --git a/src/tint/lang/wgsl/intrinsic/BUILD.gn b/src/tint/lang/wgsl/intrinsic/BUILD.gn
index b92437f..602d712 100644
--- a/src/tint/lang/wgsl/intrinsic/BUILD.gn
+++ b/src/tint/lang/wgsl/intrinsic/BUILD.gn
@@ -53,6 +53,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/intrinsic",
diff --git a/src/tint/lang/wgsl/ls/BUILD.bazel b/src/tint/lang/wgsl/ls/BUILD.bazel
index eb9413a..88b6f28 100644
--- a/src/tint/lang/wgsl/ls/BUILD.bazel
+++ b/src/tint/lang/wgsl/ls/BUILD.bazel
@@ -123,6 +123,7 @@
     "symbols_test.cc",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/type",
diff --git a/src/tint/lang/wgsl/ls/BUILD.cmake b/src/tint/lang/wgsl/ls/BUILD.cmake
index 434ee98..d17f759 100644
--- a/src/tint/lang/wgsl/ls/BUILD.cmake
+++ b/src/tint/lang/wgsl/ls/BUILD.cmake
@@ -132,6 +132,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_wgsl_ls_test test
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
diff --git a/src/tint/lang/wgsl/ls/BUILD.gn b/src/tint/lang/wgsl/ls/BUILD.gn
index c622e6a..595c0be 100644
--- a/src/tint/lang/wgsl/ls/BUILD.gn
+++ b/src/tint/lang/wgsl/ls/BUILD.gn
@@ -126,6 +126,7 @@
       deps = [
         "${dawn_root}/src/utils:utils",
         "${tint_src_dir}:gmock_and_gtest",
+        "${tint_src_dir}/api/common",
         "${tint_src_dir}/lang/core",
         "${tint_src_dir}/lang/core/constant",
         "${tint_src_dir}/lang/core/type",
diff --git a/src/tint/lang/wgsl/writer/BUILD.cmake b/src/tint/lang/wgsl/writer/BUILD.cmake
index 4bb1316..e2600d2 100644
--- a/src/tint/lang/wgsl/writer/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/BUILD.cmake
@@ -150,6 +150,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_wgsl_writer_fuzz fuzz
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
diff --git a/src/tint/lang/wgsl/writer/BUILD.gn b/src/tint/lang/wgsl/writer/BUILD.gn
index 7afa8bb..4811ba5 100644
--- a/src/tint/lang/wgsl/writer/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/BUILD.gn
@@ -130,6 +130,7 @@
     sources = []
     deps = [
       "${dawn_root}/src/utils:utils",
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
       "${tint_src_dir}/lang/core/type",
diff --git a/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel b/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel
index 36dcbb2..d229abd 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel
@@ -45,6 +45,7 @@
     "ast_printer.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/type",
diff --git a/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake b/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake
index 25cd4d5..9d37a28 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake
@@ -46,6 +46,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_wgsl_writer_ast_printer lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
diff --git a/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn b/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn
index 06ae3a0..e5a7bd8 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn
@@ -50,6 +50,7 @@
     ]
     deps = [
       "${dawn_root}/src/utils:utils",
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
       "${tint_src_dir}/lang/core/type",
diff --git a/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.bazel b/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.bazel
index 4e2b4ee..b09245a 100644
--- a/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.bazel
@@ -45,6 +45,7 @@
     "syntax_tree_printer.h",
   ],
   deps = [
+    "//src/tint/api/common",
     "//src/tint/lang/core",
     "//src/tint/lang/core/constant",
     "//src/tint/lang/core/type",
diff --git a/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.cmake b/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.cmake
index a7dd12b..db8df55 100644
--- a/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.cmake
@@ -44,6 +44,7 @@
 )
 
 tint_target_add_dependencies(tint_lang_wgsl_writer_syntax_tree_printer lib
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
diff --git a/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.gn b/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.gn
index 391e77d..243dd55 100644
--- a/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/syntax_tree_printer/BUILD.gn
@@ -46,6 +46,7 @@
   ]
   deps = [
     "${dawn_root}/src/utils:utils",
+    "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
     "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/type",