Import Tint changes from Dawn

Changes:
  - 11e2571aedb28e66100c0528c4d86503f057f4f4 tint: Minor improvements by Antonio Maiorano <amaiorano@google.com>
  - 8b4ed5093ed872cc07a874d00893da2f6bad2927 tint: Fix / remove tests that access OOB by Ben Clayton <bclayton@google.com>
  - 72340d00ce890b4fe6205aa0dd3f013705326d8e Add location into `sem::Parameter`. by dan sinclair <dsinclair@chromium.org>
  - 766a458f53116baf45263d64324ac6a991cf2073 Store location value into `sem::Function`. by dan sinclair <dsinclair@chromium.org>
  - c8278e5cbda0f3697379ebf46376aa83948b85c5 tint/transform: Fix hasher return type by Ben Clayton <bclayton@google.com>
  - 97c9e0ba9f975054cede16f0709da9182a4fc2a0 Store struct member location into the sem. by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: 11e2571aedb28e66100c0528c4d86503f057f4f4
Change-Id: I2bbca8c226e8817015d7af41c6c37035a56c4702
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/101501
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/number.h b/src/tint/number.h
index 5321158..29ae227 100644
--- a/src/tint/number.h
+++ b/src/tint/number.h
@@ -78,16 +78,18 @@
     std::is_floating_point_v<U> || std::is_same_v<T, detail::NumberKindF16>;
 
 /// Evaluates to true iff T or Number<T> is an integral type.
-template <typename T, typename U = std::conditional_t<IsNumber<T>, UnwrapNumber<T>, T>>
-constexpr bool IsIntegral = std::is_integral_v<U>;
+template <typename T>
+constexpr bool IsIntegral = std::is_integral_v<UnwrapNumber<T>>;
 
 /// Evaluates to true iff T or Number<T> is a signed integer type.
-template <typename T, typename U = std::conditional_t<IsNumber<T>, UnwrapNumber<T>, T>>
-constexpr bool IsSignedIntegral = std::is_integral_v<U> && std::is_signed_v<U>;
+template <typename T>
+constexpr bool IsSignedIntegral =
+    std::is_integral_v<UnwrapNumber<T>> && std::is_signed_v<UnwrapNumber<T>>;
 
 /// Evaluates to true iff T or Number<T> is an unsigned integer type.
-template <typename T, typename U = std::conditional_t<IsNumber<T>, UnwrapNumber<T>, T>>
-constexpr bool IsUnsignedIntegral = std::is_integral_v<U> && std::is_unsigned_v<U>;
+template <typename T>
+constexpr bool IsUnsignedIntegral =
+    std::is_integral_v<UnwrapNumber<T>> && std::is_unsigned_v<UnwrapNumber<T>>;
 
 /// Evaluates to true iff T is an integer type, floating-point type or is NumberKindF16.
 template <typename T>
diff --git a/src/tint/resolver/array_accessor_test.cc b/src/tint/resolver/array_accessor_test.cc
index a07f6df..f63bb1f 100644
--- a/src/tint/resolver/array_accessor_test.cc
+++ b/src/tint/resolver/array_accessor_test.cc
@@ -102,7 +102,7 @@
 TEST_F(ResolverIndexAccessorTest, Matrix) {
     GlobalVar("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
 
-    auto* acc = IndexAccessor("my_var", 2_i);
+    auto* acc = IndexAccessor("my_var", 1_i);
     WrapInFunction(acc);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -123,7 +123,7 @@
 TEST_F(ResolverIndexAccessorTest, Matrix_BothDimensions) {
     GlobalVar("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
 
-    auto* acc = IndexAccessor(IndexAccessor("my_var", 2_i), 1_i);
+    auto* acc = IndexAccessor(IndexAccessor("my_var", 0_i), 1_i);
     WrapInFunction(acc);
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/resolver/const_eval_test.cc b/src/tint/resolver/const_eval_test.cc
index d21d60a..1f745a3 100644
--- a/src/tint/resolver/const_eval_test.cc
+++ b/src/tint/resolver/const_eval_test.cc
@@ -99,6 +99,7 @@
     return std::move(v1);
 }
 
+// Concats vectors `vs` into `v1`
 template <typename Vec, typename... Vecs>
 void ConcatInto(Vec& v1, Vecs&&... vs) {
     auto total_size = v1.size() + (vs.size() + ...);
@@ -106,6 +107,7 @@
     (std::move(vs.begin(), vs.end(), std::back_inserter(v1)), ...);
 }
 
+// Concats vectors `vs` into `v1` iff `condition` is true
 template <bool condition, typename Vec, typename... Vecs>
 void ConcatIntoIf([[maybe_unused]] Vec& v1, [[maybe_unused]] Vecs&&... vs) {
     if constexpr (condition) {
diff --git a/src/tint/resolver/inferred_type_test.cc b/src/tint/resolver/inferred_type_test.cc
index 83ca0bd..047ac78 100644
--- a/src/tint/resolver/inferred_type_test.cc
+++ b/src/tint/resolver/inferred_type_test.cc
@@ -149,11 +149,11 @@
     auto* member = Member("x", ty.i32());
     auto* str = Structure("S", utils::Vector{member});
 
-    auto* expected_type =
-        create<sem::Struct>(str, str->name,
-                            sem::StructMemberList{create<sem::StructMember>(
-                                member, member->symbol, create<sem::I32>(), 0u, 0u, 0u, 4u)},
-                            0u, 4u, 4u);
+    auto* expected_type = create<sem::Struct>(
+        str, str->name,
+        sem::StructMemberList{create<sem::StructMember>(member, member->symbol, create<sem::I32>(),
+                                                        0u, 0u, 0u, 4u, std::nullopt)},
+        0u, 4u, 4u);
 
     auto* ctor_expr = Construct(ty.Of(str));
 
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index 86b44a4..4db3cb4 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -793,7 +793,8 @@
             /* index */ static_cast<uint32_t>(members.size()),
             /* offset */ offset,
             /* align */ align,
-            /* size */ size));
+            /* size */ size,
+            /* location */ std::nullopt));
         offset += size;
     }
     uint32_t size_without_padding = offset;
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 239efd5..af22c53 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -954,14 +954,6 @@
     Method::kBitcastF32Arg,
 };
 
-/// Methods that support abstract-integer materialization
-/// Note: Doesn't contain kWorkgroupSize or kArrayLength as they have tighter constraints on the
-///       range of allowed integer values.
-constexpr Method kAIntMethods[] = {
-    Method::kSwitch,
-    Method::kIndex,
-};
-
 /// Methods that support vector materialization
 constexpr Method kVectorMethods[] = {
     Method::kLet,          Method::kVar, Method::kBuiltinArg, Method::kBitcastVec3F32Arg,
@@ -1039,11 +1031,36 @@
             Types<f32M, AFloatM>(AFloat(-kSubnormalF32), -kSubnormalF32),                       //
         })));
 
+INSTANTIATE_TEST_SUITE_P(MaterializeAInt,
+                         MaterializeAbstractNumericToDefaultType,
+                         testing::Combine(testing::Values(Expectation::kMaterialize),
+                                          testing::Values(Method::kWorkgroupSize,
+                                                          Method::kArrayLength),
+                                          testing::ValuesIn(std::vector<Data>{
+                                              Types<i32, AInt>(1_a, 1.0),          //
+                                              Types<i32, AInt>(10_a, 10.0),        //
+                                              Types<i32, AInt>(100_a, 100.0),      //
+                                              Types<i32, AInt>(1000_a, 1000.0),    //
+                                              Types<i32, AInt>(10000_a, 10000.0),  //
+                                              Types<i32, AInt>(65535_a, 65535.0),  //
+                                          })));
+
+INSTANTIATE_TEST_SUITE_P(MaterializeAIntIndex,
+                         MaterializeAbstractNumericToDefaultType,
+                         testing::Combine(testing::Values(Expectation::kMaterialize),
+                                          testing::Values(Method::kIndex),
+                                          testing::ValuesIn(std::vector<Data>{
+                                              Types<i32, AInt>(0_a, 0.0),  //
+                                              Types<i32, AInt>(1_a, 1.0),  //
+                                              Types<i32, AInt>(2_a, 2.0),  //
+                                              Types<i32, AInt>(3_a, 3.0),  //
+                                          })));
+
 INSTANTIATE_TEST_SUITE_P(
-    MaterializeAInt,
+    MaterializeAIntSwitch,
     MaterializeAbstractNumericToDefaultType,
     testing::Combine(testing::Values(Expectation::kMaterialize),
-                     testing::ValuesIn(kAIntMethods),
+                     testing::Values(Method::kSwitch),
                      testing::ValuesIn(std::vector<Data>{
                          Types<i32, AInt>(0_a, 0.0),                              //
                          Types<i32, AInt>(10_a, 10.0),                            //
@@ -1133,7 +1150,10 @@
     AIntValueCannotBeRepresented,
     MaterializeAbstractNumericToDefaultType,
     testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented),
-                     testing::ValuesIn(kAIntMethods),
+                     testing::Values(Method::kWorkgroupSize,
+                                     Method::kArrayLength,
+                                     Method::kSwitch,
+                                     Method::kIndex),
                      testing::ValuesIn(std::vector<Data>{
                          Types<i32, AInt>(0_a, static_cast<double>(i32::kHighestValue) + 1),  //
                          Types<i32, AInt>(0_a, static_cast<double>(i32::kLowestValue) - 1),   //
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 1599334..1eb3b35 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -716,9 +716,14 @@
         }
     }
 
-    auto* sem = builder_->create<sem::Parameter>(param, index, ty, ast::StorageClass::kNone,
-                                                 ast::Access::kUndefined,
-                                                 sem::ParameterUsage::kNone, binding_point);
+    std::optional<uint32_t> location;
+    if (auto* l = ast::GetAttribute<ast::LocationAttribute>(param->attributes)) {
+        location = l->value;
+    }
+
+    auto* sem = builder_->create<sem::Parameter>(
+        param, index, ty, ast::StorageClass::kNone, ast::Access::kUndefined,
+        sem::ParameterUsage::kNone, binding_point, location);
     builder_->Sem().Add(param, sem);
     return sem;
 }
@@ -906,6 +911,19 @@
         return_type = builder_->create<sem::Void>();
     }
 
+    // Determine if the return type has a location
+    std::optional<uint32_t> return_location;
+    for (auto* attr : decl->return_type_attributes) {
+        Mark(attr);
+
+        if (auto* a = attr->As<ast::LocationAttribute>()) {
+            return_location = a->value;
+        }
+    }
+    if (!validator_.NoDuplicateAttributes(decl->attributes)) {
+        return nullptr;
+    }
+
     if (auto* str = return_type->As<sem::Struct>()) {
         if (!ApplyStorageClassUsageToType(ast::StorageClass::kNone, str, decl->source)) {
             AddNote(
@@ -929,7 +947,8 @@
         }
     }
 
-    auto* func = builder_->create<sem::Function>(decl, return_type, std::move(parameters));
+    auto* func =
+        builder_->create<sem::Function>(decl, return_type, return_location, std::move(parameters));
     builder_->Sem().Add(decl, func);
 
     TINT_SCOPED_ASSIGNMENT(current_function_, func);
@@ -968,13 +987,7 @@
     for (auto* attr : decl->attributes) {
         Mark(attr);
     }
-    if (!validator_.NoDuplicateAttributes(decl->attributes)) {
-        return nullptr;
-    }
 
-    for (auto* attr : decl->return_type_attributes) {
-        Mark(attr);
-    }
     if (!validator_.NoDuplicateAttributes(decl->return_type_attributes)) {
         return nullptr;
     }
@@ -2747,6 +2760,7 @@
         bool has_offset_attr = false;
         bool has_align_attr = false;
         bool has_size_attr = false;
+        std::optional<uint32_t> location;
         for (auto* attr : member->attributes) {
             Mark(attr);
             if (auto* o = attr->As<ast::StructMemberOffsetAttribute>()) {
@@ -2760,11 +2774,7 @@
                 align = 1;
                 has_offset_attr = true;
             } else if (auto* a = attr->As<ast::StructMemberAlignAttribute>()) {
-                const auto* expr = Expression(a->align);
-                if (!expr) {
-                    return nullptr;
-                }
-                auto* materialized = Materialize(expr);
+                auto* materialized = Materialize(Expression(a->align));
                 if (!materialized) {
                     return nullptr;
                 }
@@ -2790,6 +2800,8 @@
                 }
                 size = s->size;
                 has_size_attr = true;
+            } else if (auto* l = attr->As<ast::LocationAttribute>()) {
+                location = l->value;
             }
         }
 
@@ -2811,7 +2823,7 @@
         auto* sem_member = builder_->create<sem::StructMember>(
             member, member->symbol, type, static_cast<uint32_t>(sem_members.size()),
             static_cast<uint32_t>(offset), static_cast<uint32_t>(align),
-            static_cast<uint32_t>(size));
+            static_cast<uint32_t>(size), location);
         builder_->Sem().Add(member, sem_member);
         sem_members.emplace_back(sem_member);
 
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 72d741e..523dd89 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -773,6 +773,39 @@
     EXPECT_TRUE(func_sem->ReturnType()->Is<sem::Void>());
 }
 
+TEST_F(ResolverTest, Function_Parameters_Locations) {
+    auto* param_a = Param("a", ty.f32(), utils::Vector{Location(3)});
+    auto* param_b = Param("b", ty.u32(), utils::Vector{Builtin(ast::BuiltinValue::kVertexIndex)});
+    auto* param_c = Param("c", ty.u32(), utils::Vector{Location(1)});
+
+    GlobalVar("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+    auto* func = Func("my_func",
+                      utils::Vector{
+                          param_a,
+                          param_b,
+                          param_c,
+                      },
+                      ty.vec4<f32>(),
+                      utils::Vector{
+                          Return("my_vec"),
+                      },
+                      utils::Vector{
+                          Stage(ast::PipelineStage::kVertex),
+                      },
+                      utils::Vector{
+                          Builtin(ast::BuiltinValue::kPosition),
+                      });
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* func_sem = Sem().Get(func);
+    ASSERT_NE(func_sem, nullptr);
+    EXPECT_EQ(func_sem->Parameters().Length(), 3u);
+    EXPECT_EQ(3u, func_sem->Parameters()[0]->Location());
+    EXPECT_FALSE(func_sem->Parameters()[1]->Location().has_value());
+    EXPECT_EQ(1u, func_sem->Parameters()[2]->Location());
+}
+
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
     auto* s = Structure("S", utils::Vector{Member("m", ty.u32())});
 
@@ -802,6 +835,45 @@
     EXPECT_EQ(vars[2]->Declaration(), priv_var);
 }
 
+TEST_F(ResolverTest, Function_ReturnType_Location) {
+    auto* func = Func("my_func", utils::Empty, ty.f32(),
+                      utils::Vector{
+                          Return(1_f),
+                      },
+                      utils::Vector{
+                          Stage(ast::PipelineStage::kFragment),
+                      },
+                      utils::Vector{
+                          Location(2),
+                      });
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* sem = Sem().Get(func);
+    ASSERT_NE(nullptr, sem);
+    EXPECT_EQ(2u, sem->ReturnLocation());
+}
+
+TEST_F(ResolverTest, Function_ReturnType_NoLocation) {
+    GlobalVar("my_vec", ty.vec4<f32>(), ast::StorageClass::kPrivate);
+    auto* func = Func("my_func", utils::Empty, ty.vec4<f32>(),
+                      utils::Vector{
+                          Return("my_vec"),
+                      },
+                      utils::Vector{
+                          Stage(ast::PipelineStage::kVertex),
+                      },
+                      utils::Vector{
+                          Builtin(ast::BuiltinValue::kPosition),
+                      });
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* sem = Sem().Get(func);
+    ASSERT_NE(nullptr, sem);
+    EXPECT_FALSE(sem->ReturnLocation());
+}
+
 TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
     auto* s = Structure("S", utils::Vector{Member("m", ty.u32())});
 
diff --git a/src/tint/resolver/struct_pipeline_stage_use_test.cc b/src/tint/resolver/struct_pipeline_stage_use_test.cc
index acf30f6..c8e77ea 100644
--- a/src/tint/resolver/struct_pipeline_stage_use_test.cc
+++ b/src/tint/resolver/struct_pipeline_stage_use_test.cc
@@ -174,6 +174,20 @@
                 UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
 }
 
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamLocationSet) {
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(3)})});
+
+    Func("main", utils::Vector{Param("param", ty.Of(s))}, ty.void_(), utils::Empty,
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* sem = TypeOf(s)->As<sem::Struct>();
+    ASSERT_NE(sem, nullptr);
+    ASSERT_EQ(1u, sem->Members().size());
+    EXPECT_EQ(3u, sem->Members()[0]->Location());
+}
+
 TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeViaAlias) {
     auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(0)})});
     auto* s_alias = Alias("S_alias", ty.Of(s));
@@ -190,5 +204,19 @@
                 UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
 }
 
+TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeLocationSet) {
+    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{Location(3)})});
+
+    Func("main", utils::Empty, ty.Of(s), utils::Vector{Return(Construct(ty.Of(s), Expr(0_f)))},
+         utils::Vector{Stage(ast::PipelineStage::kFragment)});
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+
+    auto* sem = TypeOf(s)->As<sem::Struct>();
+    ASSERT_NE(sem, nullptr);
+    ASSERT_EQ(1u, sem->Members().size());
+    EXPECT_EQ(3u, sem->Members()[0]->Location());
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/variable_test.cc b/src/tint/resolver/variable_test.cc
index 52d240c..8842141 100644
--- a/src/tint/resolver/variable_test.cc
+++ b/src/tint/resolver/variable_test.cc
@@ -466,14 +466,14 @@
     // }
     // @group(0) @binding(0) var<storage, read_write> s : S;
     // fn f() {
-    //   let p = &s.inner.arr[4];
+    //   let p = &s.inner.arr[3];
     // }
     auto* inner = Structure("Inner", utils::Vector{Member("arr", ty.array<i32, 4>())});
     auto* buf = Structure("S", utils::Vector{Member("inner", ty.Of(inner))});
     auto* storage = GlobalVar("s", ty.Of(buf), ast::StorageClass::kStorage, ast::Access::kReadWrite,
                               Binding(0_a), Group(0_a));
 
-    auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4_i);
+    auto* expr = IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 3_i);
     auto* ptr = Let("p", AddressOf(expr));
 
     WrapInFunction(ptr);
diff --git a/src/tint/sem/function.cc b/src/tint/sem/function.cc
index 97171fe..6562526 100644
--- a/src/tint/sem/function.cc
+++ b/src/tint/sem/function.cc
@@ -40,10 +40,12 @@
 
 Function::Function(const ast::Function* declaration,
                    Type* return_type,
+                   std::optional<uint32_t> return_location,
                    utils::VectorRef<Parameter*> parameters)
     : Base(return_type, SetOwner(std::move(parameters), this), EvaluationStage::kRuntime),
       declaration_(declaration),
-      workgroup_size_{WorkgroupDimension{1}, WorkgroupDimension{1}, WorkgroupDimension{1}} {}
+      workgroup_size_{WorkgroupDimension{1}, WorkgroupDimension{1}, WorkgroupDimension{1}},
+      return_location_(return_location) {}
 
 Function::~Function() = default;
 
diff --git a/src/tint/sem/function.h b/src/tint/sem/function.h
index d4cb1c7..3f7256a 100644
--- a/src/tint/sem/function.h
+++ b/src/tint/sem/function.h
@@ -16,6 +16,7 @@
 #define SRC_TINT_SEM_FUNCTION_H_
 
 #include <array>
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -60,9 +61,11 @@
     /// Constructor
     /// @param declaration the ast::Function
     /// @param return_type the return type of the function
+    /// @param return_location the location value for the return, if provided
     /// @param parameters the parameters to the function
     Function(const ast::Function* declaration,
              Type* return_type,
+             std::optional<uint32_t> return_location,
              utils::VectorRef<Parameter*> parameters);
 
     /// Destructor
@@ -254,6 +257,9 @@
     /// @return the behaviors of this function
     sem::Behaviors& Behaviors() { return behaviors_; }
 
+    /// @return the location for the return, if provided
+    std::optional<uint32_t> ReturnLocation() const { return return_location_; }
+
   private:
     Function(const Function&) = delete;
     Function(Function&&) = delete;
@@ -274,6 +280,8 @@
     std::vector<const Function*> ancestor_entry_points_;
     bool has_discard_ = false;
     sem::Behaviors behaviors_{sem::Behavior::kNext};
+
+    std::optional<uint32_t> return_location_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/sem_struct_test.cc b/src/tint/sem/sem_struct_test.cc
index 3ea14e1..453b8f7 100644
--- a/src/tint/sem/sem_struct_test.cc
+++ b/src/tint/sem/sem_struct_test.cc
@@ -105,5 +105,23 @@
 /*                               */ };)");
 }
 
+TEST_F(StructTest, Location) {
+    auto* st = Structure("st", utils::Vector{
+                                   Member("a", ty.i32(), utils::Vector{Location(1u)}),
+                                   Member("b", ty.u32()),
+                               });
+
+    auto p = Build();
+    ASSERT_TRUE(p.IsValid()) << p.Diagnostics().str();
+
+    auto* sem = p.Sem().Get(st);
+    ASSERT_EQ(2u, sem->Members().size());
+
+    EXPECT_TRUE(sem->Members()[0]->Location().has_value());
+    EXPECT_EQ(sem->Members()[0]->Location().value(), 1u);
+
+    EXPECT_FALSE(sem->Members()[1]->Location().has_value());
+}
+
 }  // namespace
 }  // namespace tint::sem
diff --git a/src/tint/sem/struct.cc b/src/tint/sem/struct.cc
index 0ace9db..7d457e8 100644
--- a/src/tint/sem/struct.cc
+++ b/src/tint/sem/struct.cc
@@ -161,14 +161,16 @@
                            uint32_t index,
                            uint32_t offset,
                            uint32_t align,
-                           uint32_t size)
+                           uint32_t size,
+                           std::optional<uint32_t> location)
     : declaration_(declaration),
       name_(name),
       type_(type),
       index_(index),
       offset_(offset),
       align_(align),
-      size_(size) {}
+      size_(size),
+      location_(location) {}
 
 StructMember::~StructMember() = default;
 
diff --git a/src/tint/sem/struct.h b/src/tint/sem/struct.h
index 62b1008..0f3213a 100644
--- a/src/tint/sem/struct.h
+++ b/src/tint/sem/struct.h
@@ -17,6 +17,7 @@
 
 #include <stdint.h>
 
+#include <optional>
 #include <string>
 #include <unordered_set>
 #include <vector>
@@ -180,13 +181,15 @@
     /// @param offset the byte offset from the base of the structure
     /// @param align the byte alignment of the member
     /// @param size the byte size of the member
+    /// @param location the location attribute, if present
     StructMember(const ast::StructMember* declaration,
                  Symbol name,
                  const sem::Type* type,
                  uint32_t index,
                  uint32_t offset,
                  uint32_t align,
-                 uint32_t size);
+                 uint32_t size,
+                 std::optional<uint32_t> location);
 
     /// Destructor
     ~StructMember() override;
@@ -219,6 +222,9 @@
     /// @returns byte size
     uint32_t Size() const { return size_; }
 
+    /// @returns the location, if set
+    std::optional<uint32_t> Location() const { return location_; }
+
   private:
     const ast::StructMember* const declaration_;
     const Symbol name_;
@@ -228,6 +234,7 @@
     const uint32_t offset_;
     const uint32_t align_;
     const uint32_t size_;
+    const std::optional<uint32_t> location_;
 };
 
 }  // namespace tint::sem
diff --git a/src/tint/sem/type_test.cc b/src/tint/sem/type_test.cc
index 837579e..56dcbff 100644
--- a/src/tint/sem/type_test.cc
+++ b/src/tint/sem/type_test.cc
@@ -54,7 +54,8 @@
                                                     /* index */ 0u,
                                                     /* offset */ 0u,
                                                     /* align */ 4u,
-                                                    /* size */ 4u),
+                                                    /* size */ 4u,
+                                                    /* location */ std::nullopt),
                                             },
                                             /* align*/ 4u,
                                             /* size*/ 4u,
diff --git a/src/tint/sem/variable.cc b/src/tint/sem/variable.cc
index f233053..67e7945 100644
--- a/src/tint/sem/variable.cc
+++ b/src/tint/sem/variable.cc
@@ -73,11 +73,13 @@
                      ast::StorageClass storage_class,
                      ast::Access access,
                      const ParameterUsage usage /* = ParameterUsage::kNone */,
-                     sem::BindingPoint binding_point /* = {} */)
+                     sem::BindingPoint binding_point /* = {} */,
+                     std::optional<uint32_t> location /* = std::nullopt */)
     : Base(declaration, type, EvaluationStage::kRuntime, storage_class, access, nullptr),
       index_(index),
       usage_(usage),
-      binding_point_(binding_point) {}
+      binding_point_(binding_point),
+      location_(location) {}
 
 Parameter::~Parameter() = default;
 
diff --git a/src/tint/sem/variable.h b/src/tint/sem/variable.h
index b511563..a0eb75e 100644
--- a/src/tint/sem/variable.h
+++ b/src/tint/sem/variable.h
@@ -15,6 +15,7 @@
 #ifndef SRC_TINT_SEM_VARIABLE_H_
 #define SRC_TINT_SEM_VARIABLE_H_
 
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -189,13 +190,15 @@
     /// @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
+    /// @param location the location value, if set
     Parameter(const ast::Parameter* declaration,
               uint32_t index,
               const sem::Type* type,
               ast::StorageClass storage_class,
               ast::Access access,
               const ParameterUsage usage = ParameterUsage::kNone,
-              sem::BindingPoint binding_point = {});
+              sem::BindingPoint binding_point = {},
+              std::optional<uint32_t> location = std::nullopt);
 
     /// Destructor
     ~Parameter() override;
@@ -222,12 +225,16 @@
     /// @returns the resource binding point for the parameter
     sem::BindingPoint BindingPoint() const { return binding_point_; }
 
+    /// @returns the location value for the parameter, if set
+    std::optional<uint32_t> Location() const { return location_; }
+
   private:
     const uint32_t index_;
     const ParameterUsage usage_;
     CallTarget const* owner_ = nullptr;
     const sem::Node* shadows_ = nullptr;
     const sem::BindingPoint binding_point_;
+    const std::optional<uint32_t> location_;
 };
 
 /// VariableUser holds the semantic information for an identifier expression
diff --git a/src/tint/transform/std140.cc b/src/tint/transform/std140.cc
index 92d3d8b..f46fdea 100644
--- a/src/tint/transform/std140.cc
+++ b/src/tint/transform/std140.cc
@@ -54,7 +54,7 @@
     /// The hash function for the DynamicIndex
     /// @param d the DynamicIndex to hash
     /// @return the hash for the given DynamicIndex
-    uint64_t operator()(const DynamicIndex& d) const { return utils::Hash(d.slot); }
+    size_t operator()(const DynamicIndex& d) const { return utils::Hash(d.slot); }
 };
 
 }  // namespace tint::utils
@@ -149,9 +149,7 @@
         struct Hasher {
             /// @param fn the LoadFnKey to hash
             /// @return the hash for the given LoadFnKey
-            uint64_t operator()(const LoadFnKey& fn) const {
-                return utils::Hash(fn.var, fn.indices);
-            }
+            size_t operator()(const LoadFnKey& fn) const { return utils::Hash(fn.var, fn.indices); }
         };
 
         /// Equality operator
diff --git a/src/tint/transform/std140_test.cc b/src/tint/transform/std140_test.cc
index 4681cdf..57e23e6 100644
--- a/src/tint/transform/std140_test.cc
+++ b/src/tint/transform/std140_test.cc
@@ -1584,7 +1584,7 @@
   let l_a_3_a_1       : Inner            = a[3].a[1];
   let l_a_0_a_2_m     : mat4x2<f32>      = a[0].a[2].m;
   let l_a_1_a_3_m_0   : vec2<f32>        = a[1].a[3].m[0];
-  let l_a_2_a_0_m_1_2 : f32              = a[2].a[0].m[1][2];
+  let l_a_2_a_0_m_1_0 : f32              = a[2].a[0].m[1][0];
 }
 )";
 
@@ -1647,7 +1647,7 @@
   let l_a_3_a_1 : Inner = conv_Inner(a[3u].a[1u]);
   let l_a_0_a_2_m : mat4x2<f32> = load_a_0_a_2_m();
   let l_a_1_a_3_m_0 : vec2<f32> = a[1u].a[3u].m_0;
-  let l_a_2_a_0_m_1_2 : f32 = a[2u].a[0u].m_1[2u];
+  let l_a_2_a_0_m_1_0 : f32 = a[2u].a[0u].m_1[0u];
 }
 )";
 
@@ -2029,7 +2029,7 @@
 @group(0) @binding(0) var<uniform> u : S;
 
 fn f() {
-    for (var i = u32(u.m[0][1]); i < u32(u.m[i][2]); i += u32(u.m[1][i])) {
+    for (var i = u32(u.m[0][0]); i < u32(u.m[i][1]); i += u32(u.m[1][i])) {
     }
 }
 )";
@@ -2050,16 +2050,16 @@
 
 @group(0) @binding(0) var<uniform> u : S_std140;
 
-fn load_u_m_p0_2(p0 : u32) -> f32 {
+fn load_u_m_p0_1(p0 : u32) -> f32 {
   switch(p0) {
     case 0u: {
-      return u.m_0[2u];
+      return u.m_0[1u];
     }
     case 1u: {
-      return u.m_1[2u];
+      return u.m_1[1u];
     }
     case 2u: {
-      return u.m_2[2u];
+      return u.m_2[1u];
     }
     default: {
       return f32();
@@ -2068,7 +2068,7 @@
 }
 
 fn f() {
-  for(var i = u32(u.m_0[1u]); (i < u32(load_u_m_p0_2(u32(i)))); i += u32(u.m_1[i])) {
+  for(var i = u32(u.m_0[0u]); (i < u32(load_u_m_p0_1(u32(i)))); i += u32(u.m_1[i])) {
   }
 }
 )";
diff --git a/src/tint/writer/spirv/builder_accessor_expression_test.cc b/src/tint/writer/spirv/builder_accessor_expression_test.cc
index ae755bb..5c06717 100644
--- a/src/tint/writer/spirv/builder_accessor_expression_test.cc
+++ b/src/tint/writer/spirv/builder_accessor_expression_test.cc
@@ -653,11 +653,11 @@
     Validate(b);
 }
 
-TEST_F(BuilderTest, Runtime_IndexAccessor_Nested_Array_f32) {
-    // var pos : array<array<f32, 2>, 3u>;
+TEST_F(BuilderTest, Runtime_IndexAccessor_Array_Vec3_f32) {
+    // var pos : array<vec3<f32>, 3u>;
     // var x = pos[1u][2u];
 
-    auto* pos = Var("pos", ty.array(ty.vec2<f32>(), 3_u));
+    auto* pos = Var("pos", ty.array(ty.vec3<f32>(), 3_a));
     auto* x = Var("x", IndexAccessor(IndexAccessor(pos, 1_u), 2_u));
     WrapInFunction(pos, x);
 
@@ -668,7 +668,7 @@
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
-%8 = OpTypeVector %9 2
+%8 = OpTypeVector %9 3
 %10 = OpTypeInt 32 0
 %11 = OpConstant %10 3
 %7 = OpTypeArray %8 %11
@@ -693,11 +693,11 @@
 }
 
 TEST_F(BuilderTest, Dynamic_IndexAccessor_Nested_Array_f32) {
-    // var pos : array<array<f32, 2>, 3u>;
+    // var pos : array<array<f32, 4>, 3u>;
     // var one = 1u;
     // var x = pos[one][2u];
 
-    auto* pos = Var("pos", ty.array(ty.vec2<f32>(), 3_u));
+    auto* pos = Var("pos", ty.array(ty.array<f32, 4>(), 3_u));
     auto* one = Var("one", Expr(2_u));
     auto* x = Var("x", IndexAccessor(IndexAccessor(pos, "one"), 2_u));
     WrapInFunction(pos, one, x);
@@ -709,27 +709,28 @@
     EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
 %1 = OpTypeFunction %2
 %9 = OpTypeFloat 32
-%8 = OpTypeVector %9 2
 %10 = OpTypeInt 32 0
-%11 = OpConstant %10 3
-%7 = OpTypeArray %8 %11
+%11 = OpConstant %10 4
+%8 = OpTypeArray %9 %11
+%12 = OpConstant %10 3
+%7 = OpTypeArray %8 %12
 %6 = OpTypePointer Function %7
-%12 = OpConstantNull %7
-%13 = OpConstant %10 2
-%15 = OpTypePointer Function %10
-%16 = OpConstantNull %10
-%18 = OpTypePointer Function %9
-%22 = OpConstantNull %9
+%13 = OpConstantNull %7
+%14 = OpConstant %10 2
+%16 = OpTypePointer Function %10
+%17 = OpConstantNull %10
+%19 = OpTypePointer Function %9
+%23 = OpConstantNull %9
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %12
-%14 = OpVariable %15 Function %16
-%21 = OpVariable %18 Function %22
+    EXPECT_EQ(DumpInstructions(b.functions()[0].variables()), R"(%5 = OpVariable %6 Function %13
+%15 = OpVariable %16 Function %17
+%22 = OpVariable %19 Function %23
 )");
-    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %14 %13
-%17 = OpLoad %10 %14
-%19 = OpAccessChain %18 %5 %17 %13
-%20 = OpLoad %9 %19
-OpStore %21 %20
+    EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()), R"(OpStore %15 %14
+%18 = OpLoad %10 %15
+%20 = OpAccessChain %19 %5 %18 %14
+%21 = OpLoad %9 %20
+OpStore %22 %21
 OpReturn
 )");