tint: Add DirectVariableAccess transform

Enables the 'chromium_experimental_full_ptr_parameters' extension to
allow passing of uniform, storage and workgroup  address-spaced
pointers as parameters, as well as pointers into sub-objects.

Bug: tint:1758
Change-Id: I8c85e6104ef4f2b9a177dec2857b1bf7f5148212
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/103860
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index a51de7b..3d10a06 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -502,6 +502,8 @@
     "transform/decompose_strided_matrix.h",
     "transform/demote_to_helper.cc",
     "transform/demote_to_helper.h",
+    "transform/direct_variable_access.cc",
+    "transform/direct_variable_access.h",
     "transform/disable_uniformity_analysis.cc",
     "transform/disable_uniformity_analysis.h",
     "transform/expand_compound_assignment.cc",
@@ -1227,6 +1229,7 @@
       "transform/decompose_strided_array_test.cc",
       "transform/decompose_strided_matrix_test.cc",
       "transform/demote_to_helper_test.cc",
+      "transform/direct_variable_access_test.cc",
       "transform/disable_uniformity_analysis_test.cc",
       "transform/expand_compound_assignment_test.cc",
       "transform/first_index_offset_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 4f193b5..cb9c96e 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -425,6 +425,8 @@
   transform/decompose_strided_matrix.h
   transform/demote_to_helper.cc
   transform/demote_to_helper.h
+  transform/direct_variable_access.cc
+  transform/direct_variable_access.h
   transform/disable_uniformity_analysis.cc
   transform/disable_uniformity_analysis.h
   transform/expand_compound_assignment.cc
@@ -1195,6 +1197,7 @@
       transform/decompose_strided_array_test.cc
       transform/decompose_strided_matrix_test.cc
       transform/demote_to_helper_test.cc
+      transform/direct_variable_access_test.cc
       transform/disable_uniformity_analysis_test.cc
       transform/expand_compound_assignment_test.cc
       transform/first_index_offset_test.cc
diff --git a/src/tint/transform/direct_variable_access.cc b/src/tint/transform/direct_variable_access.cc
new file mode 100644
index 0000000..d1c1339
--- /dev/null
+++ b/src/tint/transform/direct_variable_access.cc
@@ -0,0 +1,1216 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/transform/direct_variable_access.h"
+
+#include <algorithm>
+#include <string>
+#include <utility>
+
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/sem/abstract_int.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/index_accessor_expression.h"
+#include "src/tint/sem/member_accessor_expression.h"
+#include "src/tint/sem/module.h"
+#include "src/tint/sem/statement.h"
+#include "src/tint/sem/struct.h"
+#include "src/tint/sem/variable.h"
+#include "src/tint/transform/utils/hoist_to_decl_before.h"
+#include "src/tint/utils/reverse.h"
+#include "src/tint/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DirectVariableAccess);
+TINT_INSTANTIATE_TYPEINFO(tint::transform::DirectVariableAccess::Config);
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace {
+
+/// AccessRoot describes the root of an AccessShape.
+struct AccessRoot {
+    /// The pointer-unwrapped type of the *transformed* variable.
+    /// This may be different for pointers in 'private' and 'function' address space, as the pointer
+    /// parameter type is to the *base object* instead of the input pointer type.
+    tint::sem::Type const* type = nullptr;
+    /// The originating module-scope variable ('private', 'storage', 'uniform', 'workgroup'),
+    /// function-scope variable ('function'), or pointer parameter in the source program.
+    tint::sem::Variable const* variable = nullptr;
+    /// The address space of the variable or pointer type.
+    tint::ast::AddressSpace address_space = tint::ast::AddressSpace::kUndefined;
+};
+
+/// Inequality operator for AccessRoot
+bool operator!=(const AccessRoot& a, const AccessRoot& b) {
+    return a.type != b.type || a.variable != b.variable;
+}
+
+/// DynamicIndex is used by DirectVariableAccess::State::AccessOp to indicate an array, matrix or
+/// vector index.
+struct DynamicIndex {
+    /// The index of the expression in DirectVariableAccess::State::AccessChain::dynamic_indices
+    size_t slot = 0;
+};
+
+/// Inequality operator for DynamicIndex
+bool operator!=(const DynamicIndex& a, const DynamicIndex& b) {
+    return a.slot != b.slot;
+}
+
+/// AccessOp describes a single access in an access chain.
+/// The access is one of:
+/// Symbol        - a struct member access.
+/// DynamicIndex  - a runtime index on an array, matrix column, or vector element.
+using AccessOp = std::variant<tint::Symbol, DynamicIndex>;
+
+/// A vector of AccessOp. Describes the static "path" from a root variable to an element
+/// within the variable. Array accessors index expressions are held externally to the
+/// AccessShape, so AccessShape will be considered equal even if the array, matrix or vector
+/// index values differ.
+///
+/// For example, consider the following:
+///
+/// ```
+///           struct A {
+///               x : array<i32, 8>,
+///               y : u32,
+///           };
+///           struct B {
+///               x : i32,
+///               y : array<A, 4>
+///           };
+///           var<workgroup> C : B;
+/// ```
+///
+/// The following AccessShape would describe the following:
+///
+/// +==============================+===============+=================================+
+/// | AccessShape                  | Type          |  Expression                     |
+/// +==============================+===============+=================================+
+/// | [ Variable 'C', Symbol 'x' ] | i32           |  C.x                            |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y' ] | array<A, 4>   |  C.y                            |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | A             |  C.y[dyn_idx[0]]                |
+/// |   DynamicIndex ]             |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | array<i32, 8> |  C.y[dyn_idx[0]].x              |
+/// |   DynamicIndex, Symbol 'x' ] |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | i32           |  C.y[dyn_idx[0]].x[dyn_idx[1]]  |
+/// |   DynamicIndex, Symbol 'x',  |               |                                 |
+/// |   DynamicIndex ]             |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+/// | [ Variable 'C', Symbol 'y',  | u32           |  C.y[dyn_idx[0]].y              |
+/// |   DynamicIndex, Symbol 'y' ] |               |                                 |
+/// +------------------------------+---------------+---------------------------------+
+///
+/// Where: `dyn_idx` is the AccessChain::dynamic_indices.
+struct AccessShape {
+    // The originating variable.
+    AccessRoot root;
+    /// The chain of access ops.
+    tint::utils::Vector<AccessOp, 8> ops;
+
+    /// @returns the number of DynamicIndex operations in #ops.
+    uint32_t NumDynamicIndices() const {
+        uint32_t count = 0;
+        for (auto& op : ops) {
+            if (std::holds_alternative<DynamicIndex>(op)) {
+                count++;
+            }
+        }
+        return count;
+    }
+};
+
+/// Equality operator for AccessShape
+bool operator==(const AccessShape& a, const AccessShape& b) {
+    return !(a.root != b.root) && a.ops == b.ops;
+}
+
+/// Inequality operator for AccessShape
+bool operator!=(const AccessShape& a, const AccessShape& b) {
+    return !(a == b);
+}
+
+/// AccessChain describes a chain of access expressions originating from a variable.
+struct AccessChain : AccessShape {
+    /// The array accessor index expressions. This vector is indexed by the `DynamicIndex`s in
+    /// #indices.
+    tint::utils::Vector<const tint::sem::Expression*, 8> dynamic_indices;
+    /// If true, then this access chain is used as an argument to call a variant.
+    bool used_in_call = false;
+};
+
+}  // namespace
+
+namespace tint::utils {
+
+/// Hasher specialization for AccessRoot
+template <>
+struct Hasher<AccessRoot> {
+    /// The hash function for the AccessRoot
+    /// @param d the AccessRoot to hash
+    /// @return the hash for the given AccessRoot
+    size_t operator()(const AccessRoot& d) const { return utils::Hash(d.type, d.variable); }
+};
+
+/// Hasher specialization for DynamicIndex
+template <>
+struct Hasher<DynamicIndex> {
+    /// The hash function for the DynamicIndex
+    /// @param d the DynamicIndex to hash
+    /// @return the hash for the given DynamicIndex
+    size_t operator()(const DynamicIndex& d) const { return utils::Hash(d.slot); }
+};
+
+/// Hasher specialization for AccessShape
+template <>
+struct Hasher<AccessShape> {
+    /// The hash function for the AccessShape
+    /// @param s the AccessShape to hash
+    /// @return the hash for the given AccessShape
+    size_t operator()(const AccessShape& s) const { return utils::Hash(s.root, s.ops); }
+};
+
+}  // namespace tint::utils
+
+namespace tint::transform {
+
+/// The PIMPL state for the DirectVariableAccess transform
+struct DirectVariableAccess::State {
+    /// Constructor
+    /// @param src the source Program
+    /// @param options the transform options
+    State(const Program* src, const Options& options)
+        : ctx{&b, src, /* auto_clone_symbols */ true}, opts(options) {}
+
+    /// The main function for the transform.
+    /// @returns the ApplyResult
+    ApplyResult Run() {
+        if (!ctx.src->Sem().Module()->Extensions().Contains(
+                ast::Extension::kChromiumExperimentalFullPtrParameters)) {
+            // If the 'chromium_experimental_full_ptr_parameters' extension is not enabled, then
+            // there's nothing for this transform to do.
+            return SkipTransform;
+        }
+
+        // Stage 1:
+        // Walk all the expressions of the program, starting with the expression leaves.
+        // Whenever we find an identifier resolving to a var, pointer parameter or pointer let to
+        // another chain, start constructing an access chain. When chains are accessed, these chains
+        // are grown and moved up the expression tree. After this stage, we are left with all the
+        // expression access chains to variables that we may need to transform.
+        for (auto* node : ctx.src->ASTNodes().Objects()) {
+            if (auto* expr = sem.Get<sem::Expression>(node)) {
+                AppendAccessChain(expr);
+            }
+        }
+
+        // Stage 2:
+        // Walk the functions in dependency order, starting with the entry points.
+        // Construct the set of function 'variants' by examining the calls made by each function to
+        // their call target. Each variant holds a map of pointer parameter to access chains, and
+        // will have the pointer parameters replaced with an array of u32s, used to perform the
+        // pointer indexing in the variant.
+        // Function call pointer arguments are replaced with an array of these dynamic indices.
+        for (auto* decl : utils::Reverse(sem.Module()->DependencyOrderedDeclarations())) {
+            if (auto* fn = sem.Get<sem::Function>(decl)) {
+                auto* fn_info = FnInfoFor(fn);
+                ProcessFunction(fn, fn_info);
+                TransformFunction(fn, fn_info);
+            }
+        }
+
+        // Stage 3:
+        // Filter out access chains that do not need transforming.
+        // Ensure that chain dynamic index expressions are evaluated once at the correct place
+        ProcessAccessChains();
+
+        // Stage 4:
+        // Replace all the access chain expressions in all functions with reconstructed expression
+        // using the originating global variable, and any dynamic indices passed in to the function
+        // variant.
+        TransformAccessChainExpressions();
+
+        // Stage 5:
+        // Actually kick the clone.
+        CloneState state;
+        clone_state = &state;
+        ctx.Clone();
+        return Program(std::move(*ctx.dst));
+    }
+
+  private:
+    /// Holds symbols of the transformed pointer parameter.
+    /// If both symbols are valid, then #base_ptr and #indices are both program-unique symbols
+    /// derived from the original parameter name.
+    /// If only one symbol is valid, then this is the original parameter symbol.
+    struct PtrParamSymbols {
+        /// The symbol of the base pointer parameter.
+        Symbol base_ptr;
+        /// The symbol of the dynamic indicies parameter.
+        Symbol indices;
+    };
+
+    /// FnVariant describes a unique variant of a function, specialized by the AccessShape of the
+    /// pointer arguments - also known as the variant's "signature".
+    ///
+    /// To help understand what a variant is, consider the following WGSL:
+    ///
+    /// ```
+    /// fn F(a : ptr<storage, u32>, b : u32, c : ptr<storage, u32>) {
+    ///    return *a + b + *c;
+    /// }
+    ///
+    /// @group(0) @binding(0) var<storage> S0 : u32;
+    /// @group(0) @binding(0) var<storage> S1 : array<u32, 64>;
+    ///
+    /// fn x() {
+    ///    F(&S0, 0, &S0);       // (A)
+    ///    F(&S0, 0, &S0);       // (B)
+    ///    F(&S1[0], 1, &S0);    // (C)
+    ///    F(&S1[5], 2, &S0);    // (D)
+    ///    F(&S1[5], 3, &S1[3]); // (E)
+    ///    F(&S1[7], 4, &S1[2]); // (F)
+    /// }
+    /// ```
+    ///
+    /// Given the calls in x(), function F() will have 3 variants:
+    /// (1) F<S0,S0>                   - called by (A) and (B).
+    ///                                  Note that only 'uniform', 'storage' and 'workgroup' pointer
+    ///                                  parameters are considered for a variant signature, and so
+    ///                                  the argument for parameter 'b' is not included in the
+    ///                                  signature.
+    /// (2) F<S1[dyn_idx],S0>          - called by (C) and (D).
+    ///                                  Note that the array index value is external to the
+    ///                                  AccessShape, and so is not part of the variant signature.
+    /// (3) F<S1[dyn_idx],S1[dyn_idx]> - called by (E) and (F).
+    ///
+    /// Each variant of the function will be emitted as a separate function by the transform, and
+    /// would look something like:
+    ///
+    /// ```
+    /// // variant F<S0,S0> (1)
+    /// fn F_S0_S0(b : u32) {
+    ///    return S0 + b + S0;
+    /// }
+    ///
+    /// type S1_X = array<u32, 1>;
+    ///
+    /// // variant F<S1[dyn_idx],S0> (2)
+    /// fn F_S1_X_S0(a : S1_X, b : u32) {
+    ///    return S1[a[0]] + b + S0;
+    /// }
+    ///
+    /// // variant F<S1[dyn_idx],S1[dyn_idx]> (3)
+    /// fn F_S1_X_S1_X(a : S1_X, b : u32, c : S1_X) {
+    ///    return S1[a[0]] + b + S1[c[0]];
+    /// }
+    ///
+    /// @group(0) @binding(0) var<storage> S0 : u32;
+    /// @group(0) @binding(0) var<storage> S1 : array<u32, 64>;
+    ///
+    /// fn x() {
+    ///    F_S0_S0(0);                        // (A)
+    ///    F(&S0, 0, &S0);                    // (B)
+    ///    F_S1_X_S0(S1_X(0), 1);             // (C)
+    ///    F_S1_X_S0(S1_X(5), 2);             // (D)
+    ///    F_S1_X_S1_X(S1_X(5), 3, S1_X(3));  // (E)
+    ///    F_S1_X_S1_X(S1_X(7), 4, S1_X(2));  // (F)
+    /// }
+    /// ```
+    struct FnVariant {
+        /// The signature of the variant is a map of each of the function's 'uniform', 'storage' and
+        /// 'workgroup' pointer parameters to the caller's AccessShape.
+        using Signature = utils::Hashmap<const sem::Parameter*, AccessShape, 4>;
+
+        /// The unique name of the variant.
+        /// The symbol is in the `ctx.dst` program namespace.
+        Symbol name;
+
+        /// A map of direct calls made by this variant to the name of other function variants.
+        utils::Hashmap<const sem::Call*, Symbol, 4> calls;
+
+        /// A map of input program parameter to output parameter symbols.
+        utils::Hashmap<const sem::Parameter*, PtrParamSymbols, 4> ptr_param_symbols;
+
+        /// The declaration order of the variant, in relation to other variants of the same
+        /// function. Used to ensure deterministic ordering of the transform, as map iteration is
+        /// not deterministic between compilers.
+        size_t order = 0;
+    };
+
+    /// FnInfo holds information about a function in the input program.
+    struct FnInfo {
+        /// A map of variant signature to the variant data.
+        utils::Hashmap<FnVariant::Signature, FnVariant, 8> variants;
+        /// A map of expressions that have been hoisted to a 'let' declaration in the function.
+        utils::Hashmap<const sem::Expression*, Symbol, 8> hoisted_exprs;
+
+        /// @returns the variants of the function in a deterministically ordered vector.
+        utils::Vector<std::pair<const FnVariant::Signature*, FnVariant*>, 8> SortedVariants() {
+            utils::Vector<std::pair<const FnVariant::Signature*, FnVariant*>, 8> out;
+            out.Reserve(variants.Count());
+            for (auto it : variants) {
+                out.Push({&it.key, &it.value});
+            }
+            out.Sort([&](auto& va, auto& vb) { return va.second->order < vb.second->order; });
+            return out;
+        }
+    };
+
+    /// The program builder
+    ProgramBuilder b;
+    /// The clone context
+    CloneContext ctx;
+    /// The transform options
+    const Options& opts;
+    /// Alias to the semantic info in ctx.src
+    const sem::Info& sem = ctx.src->Sem();
+    /// Alias to the symbols in ctx.src
+    const SymbolTable& sym = ctx.src->Symbols();
+    /// Map of semantic function to the function info
+    utils::Hashmap<const sem::Function*, FnInfo*, 8> fns;
+    /// Map of AccessShape to the name of a type alias for the an array<u32, N> used for the
+    /// dynamic indices of an access chain, passed down as the transformed type of a variant's
+    /// pointer parameter.
+    utils::Hashmap<AccessShape, Symbol, 8> dynamic_index_array_aliases;
+    /// Map of semantic expression to AccessChain
+    utils::Hashmap<const sem::Expression*, AccessChain*, 32> access_chains;
+    /// Allocator for FnInfo
+    utils::BlockAllocator<FnInfo> fn_info_allocator;
+    /// Allocator for AccessChain
+    utils::BlockAllocator<AccessChain> access_chain_allocator;
+    /// Helper used for hoisting expressions to lets
+    HoistToDeclBefore hoist{ctx};
+    /// Map of string to unique symbol (no collisions in output program).
+    utils::Hashmap<std::string, Symbol, 8> unique_symbols;
+
+    /// CloneState holds pointers to the current function, variant and variant's parameters.
+    struct CloneState {
+        /// The current function being cloned
+        FnInfo* current_function = nullptr;
+        /// The current function variant being built
+        FnVariant* current_variant = nullptr;
+        /// The signature of the current function variant being built
+        const FnVariant::Signature* current_variant_sig = nullptr;
+    };
+
+    /// The clone state.
+    /// Only valid during the lifetime of the CloneContext::Clone().
+    CloneState* clone_state = nullptr;
+
+    /// AppendAccessChain creates or extends an existing AccessChain for the given expression,
+    /// modifying the #access_chains map.
+    void AppendAccessChain(const sem::Expression* expr) {
+        // take_chain moves the AccessChain from the expression `from` to the expression `expr`.
+        // Returns nullptr if `from` did not hold an access chain.
+        auto take_chain = [&](const sem::Expression* from) -> AccessChain* {
+            if (auto* chain = AccessChainFor(from)) {
+                access_chains.Remove(from);
+                access_chains.Add(expr, chain);
+                return chain;
+            }
+            return nullptr;
+        };
+
+        Switch(
+            expr,
+            [&](const sem::VariableUser* user) {
+                // Expression resolves to a variable.
+                auto* variable = user->Variable();
+
+                auto create_new_chain = [&] {
+                    auto* chain = access_chain_allocator.Create();
+                    chain->root.variable = variable;
+                    chain->root.type = variable->Type();
+                    chain->root.address_space = variable->AddressSpace();
+                    if (auto* ptr = chain->root.type->As<sem::Pointer>()) {
+                        chain->root.address_space = ptr->AddressSpace();
+                    }
+                    access_chains.Add(expr, chain);
+                };
+
+                Switch(
+                    variable->Declaration(),
+                    [&](const ast::Var*) {
+                        if (variable->AddressSpace() != ast::AddressSpace::kHandle) {
+                            // Start a new access chain for the non-handle 'var' access
+                            create_new_chain();
+                        }
+                    },
+                    [&](const ast::Parameter*) {
+                        if (variable->Type()->Is<sem::Pointer>()) {
+                            // Start a new access chain for the pointer parameter access
+                            create_new_chain();
+                        }
+                    },
+                    [&](const ast::Let*) {
+                        if (variable->Type()->Is<sem::Pointer>()) {
+                            // variable is a pointer-let.
+                            auto* init = sem.Get(variable->Declaration()->initializer);
+                            // Note: We do not use take_chain() here, as we need to preserve the
+                            // AccessChain on the let's initializer, as the let needs its
+                            // initializer updated, and the let may be used multiple times. Instead
+                            // we copy the let's AccessChain into a a new AccessChain.
+                            if (auto* init_chain = AccessChainFor(init)) {
+                                access_chains.Add(expr, access_chain_allocator.Create(*init_chain));
+                            }
+                        }
+                    });
+            },
+            [&](const sem::StructMemberAccess* a) {
+                // Structure member access.
+                // Append the Symbol of the member name to the chain, and move the chain to the
+                // member access expression.
+                if (auto* chain = take_chain(a->Object())) {
+                    chain->ops.Push(a->Member()->Name());
+                }
+            },
+            [&](const sem::IndexAccessorExpression* a) {
+                // Array, matrix or vector index.
+                // Store the index expression into AccessChain::dynamic_indices, append a
+                // DynamicIndex to the chain, and move the chain to the index accessor expression.
+                if (auto* chain = take_chain(a->Object())) {
+                    chain->ops.Push(DynamicIndex{chain->dynamic_indices.Length()});
+                    chain->dynamic_indices.Push(a->Index());
+                }
+            },
+            [&](const sem::Expression* e) {
+                if (auto* unary = e->Declaration()->As<ast::UnaryOpExpression>()) {
+                    // Unary op.
+                    // If this is a '&' or '*', simply move the chain to the unary op expression.
+                    if (unary->op == ast::UnaryOp::kAddressOf ||
+                        unary->op == ast::UnaryOp::kIndirection) {
+                        take_chain(sem.Get(unary->expr));
+                    }
+                }
+            });
+    }
+
+    /// MaybeHoistDynamicIndices examines the AccessChain::dynamic_indices member of @p chain,
+    /// hoisting all expressions to their own uniquely named 'let' if none of the following are
+    /// true:
+    /// 1. The index expression is a constant value.
+    /// 2. The index expression's statement is the same as @p usage.
+    /// 3. The index expression is an identifier resolving to a 'let', 'const' or parameter, AND
+    ///    that identifier resolves to the same variable at @p usage.
+    ///
+    /// A dynamic index will only be hoisted once. The hoisting applies to all variants of the
+    /// function that holds the dynamic index expression.
+    void MaybeHoistDynamicIndices(AccessChain* chain, const sem::Statement* usage) {
+        for (auto& idx : chain->dynamic_indices) {
+            if (idx->ConstantValue()) {
+                // Dynamic index is constant.
+                continue;  // Hoisting not required.
+            }
+
+            if (idx->Stmt() == usage) {
+                // The index expression is owned by the statement of usage.
+                continue;  // Hoisting not required
+            }
+
+            if (auto* idx_variable_user = idx->UnwrapMaterialize()->As<sem::VariableUser>()) {
+                auto* idx_variable = idx_variable_user->Variable();
+                if (idx_variable->Declaration()->IsAnyOf<ast::Let, ast::Parameter>()) {
+                    // Dynamic index is an immutable variable
+                    continue;  // Hoisting not required.
+                }
+            }
+
+            // The dynamic index needs to be hoisted (if it hasn't been already).
+            auto fn = FnInfoFor(idx->Stmt()->Function());
+            fn->hoisted_exprs.GetOrCreate(idx, [=] {
+                // Create a name for the new 'let'
+                auto name = b.Symbols().New("ptr_index_save");
+                // Insert a new 'let' just above the dynamic index statement.
+                hoist.InsertBefore(idx->Stmt(), [this, idx, name] {
+                    return b.Decl(b.Let(name, ctx.CloneWithoutTransform(idx->Declaration())));
+                });
+                return name;
+            });
+        }
+    }
+
+    /// BuildDynamicIndex builds the AST expression node for the dynamic index expression used in an
+    /// AccessChain. This is similar to just cloning the expression, but BuildDynamicIndex()
+    /// also:
+    /// * Collapses constant value index expressions down to the computed value. This acts as an
+    ///   constant folding optimization and reduces noise from the transform.
+    /// * Casts the resulting expression to a u32 if @p cast_to_u32 is true, and the expression type
+    ///   isn't implicitly usable as a u32. This is to help feed the expression into a
+    ///   `array<u32, N>` argument passed to a callee variant function.
+    const ast::Expression* BuildDynamicIndex(const sem::Expression* idx, bool cast_to_u32) {
+        if (auto* val = idx->ConstantValue()) {
+            // Expression evaluated to a constant value. Just emit that constant.
+            return b.Expr(val->As<AInt>());
+        }
+
+        // Expression is not a constant, clone the expression.
+        // Note: If the dynamic index expression was hoisted to a let, then cloning will return an
+        // identifier expression to the hoisted let.
+        auto* expr = ctx.Clone(idx->Declaration());
+
+        if (cast_to_u32) {
+            // The index may be fed to a dynamic index array<u32, N> argument, so the index
+            // expression may need casting to u32.
+            if (!idx->UnwrapMaterialize()
+                     ->Type()
+                     ->UnwrapRef()
+                     ->IsAnyOf<sem::U32, sem::AbstractInt>()) {
+                expr = b.Construct(b.ty.u32(), expr);
+            }
+        }
+
+        return expr;
+    }
+
+    /// ProcessFunction scans the direct calls made by the function @p fn, adding new variants to
+    /// the callee functions and transforming the call expression to pass dynamic indices instead of
+    /// true pointers.
+    /// If the function @p fn has pointer parameters that must be transformed to a caller variant,
+    /// and the function is not called, then the function is dropped from the output of the
+    /// transform, as it cannot be generated.
+    /// @note ProcessFunction must be called in dependency order for the program, starting with the
+    /// entry points.
+    void ProcessFunction(const sem::Function* fn, FnInfo* fn_info) {
+        if (fn_info->variants.IsEmpty()) {
+            // Function has no variants pre-generated by callers.
+            if (MustBeCalled(fn)) {
+                // Drop the function, as it wasn't called and cannot be generated.
+                ctx.Remove(ctx.src->AST().GlobalDeclarations(), fn->Declaration());
+                return;
+            }
+
+            // Function was not called. Create a single variant with an empty signature.
+            FnVariant variant;
+            variant.name = ctx.Clone(fn->Declaration()->symbol);
+            variant.order = 0;  // Unaltered comes first.
+            fn_info->variants.Add(FnVariant::Signature{}, std::move(variant));
+        }
+
+        // Process each of the direct calls made by this function.
+        for (auto* call : fn->DirectCalls()) {
+            ProcessCall(fn_info, call);
+        }
+    }
+
+    /// ProcessCall creates new variants of the callee function by permuting the call for each of
+    /// the variants of @p caller. ProcessCall also registers the clone callback to transform the
+    /// call expression to pass dynamic indices instead of true pointers.
+    void ProcessCall(FnInfo* caller, const sem::Call* call) {
+        auto* target = call->Target()->As<sem::Function>();
+        if (!target) {
+            // Call target is not a user-declared function.
+            return;  // Not interested in this call.
+        }
+
+        if (!HasPointerParameter(target)) {
+            return;  // Not interested in this call.
+        }
+
+        bool call_needs_transforming = false;
+
+        // Build the call target function variant for each variant of the caller.
+        for (auto caller_variant_it : caller->SortedVariants()) {
+            auto& caller_signature = *caller_variant_it.first;
+            auto& caller_variant = *caller_variant_it.second;
+
+            // Build the target variant's signature.
+            FnVariant::Signature target_signature;
+            for (size_t i = 0; i < call->Arguments().Length(); i++) {
+                const auto* arg = call->Arguments()[i];
+                const auto* param = target->Parameters()[i];
+                const auto* param_ty = param->Type()->As<sem::Pointer>();
+                if (!param_ty) {
+                    continue;  // Parameter type is not a pointer.
+                }
+
+                // Fetch the access chain for the argument.
+                auto* arg_chain = AccessChainFor(arg);
+                if (!arg_chain) {
+                    continue;  // Argument does not have an access chain
+                }
+
+                // Construct the absolute AccessShape by considering the AccessShape of the caller
+                // variant's argument. This will propagate back through pointer parameters, to the
+                // outermost caller.
+                auto absolute = AbsoluteAccessShape(caller_signature, *arg_chain);
+
+                // If the address space of the root variable of the access chain does not require
+                // transformation, then there's nothing to do.
+                if (!AddressSpaceRequiresTransform(absolute.root.address_space)) {
+                    continue;
+                }
+
+                // Record that this chain was used in a function call.
+                // This preserves the chain during the access chain filtering stage.
+                arg_chain->used_in_call = true;
+
+                if (IsPrivateOrFunction(absolute.root.address_space)) {
+                    // Pointers in 'private' and 'function' address spaces need to be passed by
+                    // pointer argument.
+                    absolute.root.variable = param;
+                }
+
+                // Add the parameter's absolute AccessShape to the target's signature.
+                target_signature.Add(param, std::move(absolute));
+            }
+
+            // Construct a new FnVariant if this is the first caller of the target signature
+            auto* target_info = FnInfoFor(target);
+            auto& target_variant = target_info->variants.GetOrCreate(target_signature, [&] {
+                if (target_signature.IsEmpty()) {
+                    // Call target does not require any argument changes.
+                    FnVariant variant;
+                    variant.name = ctx.Clone(target->Declaration()->symbol);
+                    variant.order = 0;  // Unaltered comes first.
+                    return variant;
+                }
+
+                // Build an appropriate variant function name.
+                // This is derived from the original function name and the pointer parameter
+                // chains.
+                std::stringstream ss;
+                ss << ctx.src->Symbols().NameFor(target->Declaration()->symbol);
+                for (auto* param : target->Parameters()) {
+                    if (auto indices = target_signature.Find(param)) {
+                        ss << "_" << AccessShapeName(*indices);
+                    }
+                }
+
+                // Build the pointer parameter symbols.
+                utils::Hashmap<const sem::Parameter*, PtrParamSymbols, 4> ptr_param_symbols;
+                for (auto param_it : target_signature) {
+                    auto* param = param_it.key;
+                    auto& shape = param_it.value;
+
+                    // Parameter needs replacing with either zero, one or two parameters:
+                    // If the parameter is in the 'private' or 'function' address space, then the
+                    // originating pointer is always passed down. This always comes first.
+                    // If the access chain has dynamic indices, then we create an array<u32, N>
+                    // parameter to hold the dynamic indices.
+                    bool requires_base_ptr_param = IsPrivateOrFunction(shape.root.address_space);
+                    bool requires_indices_param = shape.NumDynamicIndices() > 0;
+
+                    PtrParamSymbols symbols;
+                    if (requires_base_ptr_param && requires_indices_param) {
+                        auto original_name = param->Declaration()->symbol;
+                        symbols.base_ptr = UniqueSymbolWithSuffix(original_name, "_base");
+                        symbols.indices = UniqueSymbolWithSuffix(original_name, "_indices");
+                    } else if (requires_base_ptr_param) {
+                        symbols.base_ptr = ctx.Clone(param->Declaration()->symbol);
+                    } else if (requires_indices_param) {
+                        symbols.indices = ctx.Clone(param->Declaration()->symbol);
+                    }
+
+                    // Remember this base pointer name.
+                    ptr_param_symbols.Add(param, symbols);
+                }
+
+                // Build the variant.
+                FnVariant variant;
+                variant.name = b.Symbols().New(ss.str());
+                variant.order = target_info->variants.Count() + 1;
+                variant.ptr_param_symbols = std::move(ptr_param_symbols);
+                return variant;
+            });
+
+            // Record the call made by caller variant to the target variant.
+            caller_variant.calls.Add(call, target_variant.name);
+            if (!target_signature.IsEmpty()) {
+                // The call expression will need transforming for at least one caller variant.
+                call_needs_transforming = true;
+            }
+        }
+
+        if (call_needs_transforming) {
+            // Register the clone callback to correctly transform the call expression into the
+            // appropriate variant calls.
+            TransformCall(call);
+        }
+    }
+
+    /// @returns true if the address space @p address_space requires transforming given the
+    /// transform's options.
+    bool AddressSpaceRequiresTransform(ast::AddressSpace address_space) const {
+        switch (address_space) {
+            case ast::AddressSpace::kUniform:
+            case ast::AddressSpace::kStorage:
+            case ast::AddressSpace::kWorkgroup:
+                return true;
+            case ast::AddressSpace::kPrivate:
+                return opts.transform_private;
+            case ast::AddressSpace::kFunction:
+                return opts.transform_function;
+            default:
+                return false;
+        }
+    }
+
+    /// @returns the AccessChain for the expression @p expr, or nullptr if the expression does
+    /// not hold an access chain.
+    AccessChain* AccessChainFor(const sem::Expression* expr) const {
+        if (auto chain = access_chains.Find(expr)) {
+            return *chain;
+        }
+        return nullptr;
+    }
+
+    /// @returns the absolute AccessShape for @p indices, by replacing the originating pointer
+    /// parameter with the AccessChain of variant's signature.
+    AccessShape AbsoluteAccessShape(const FnVariant::Signature& signature,
+                                    const AccessShape& shape) const {
+        if (auto* root_param = shape.root.variable->As<sem::Parameter>()) {
+            if (auto incoming_chain = signature.Find(root_param)) {
+                // Access chain originates from a parameter, which will be transformed into an array
+                // of dynamic indices. Concatenate the signature's AccessShape for the parameter
+                // to the chain's indices, skipping over the chain's initial parameter index.
+                auto absolute = *incoming_chain;
+                for (auto& op : shape.ops) {
+                    absolute.ops.Push(op);
+                }
+                return absolute;
+            }
+        }
+
+        // Chain does not originate from a parameter, so is already absolute.
+        return shape;
+    }
+
+    /// TransformFunction registers the clone callback to transform the function @p fn into the
+    /// (potentially multiple) function's variants. TransformFunction will assign the current
+    /// function and variant to #clone_state, which can be used by the other clone callbacks.
+    void TransformFunction(const sem::Function* fn, FnInfo* fn_info) {
+        // Register a custom handler for the specific function
+        ctx.Replace(fn->Declaration(), [this, fn, fn_info] {
+            // For the scope of this lambda, assign current_function to fn_info.
+            TINT_SCOPED_ASSIGNMENT(clone_state->current_function, fn_info);
+
+            // This callback expects a single function returned. As we're generating potentially
+            // many variant functions, keep a record of the last created variant, and explicitly add
+            // this to the module if it isn't the last. We'll return the last created variant,
+            // taking the place of the original function.
+            const ast::Function* pending_variant = nullptr;
+
+            // For each variant of fn...
+            for (auto variant_it : fn_info->SortedVariants()) {
+                if (pending_variant) {
+                    b.AST().AddFunction(pending_variant);
+                }
+
+                auto& variant_sig = *variant_it.first;
+                auto& variant = *variant_it.second;
+
+                // For the rest of this scope, assign the current variant and variant signature.
+                TINT_SCOPED_ASSIGNMENT(clone_state->current_variant_sig, &variant_sig);
+                TINT_SCOPED_ASSIGNMENT(clone_state->current_variant, &variant);
+
+                // Build the variant's parameters.
+                // Pointer parameters in the 'uniform', 'storage' or 'workgroup' address space are
+                // either replaced with an array of dynamic indices, or are dropped (if there are no
+                // dynamic indices).
+                utils::Vector<const ast::Parameter*, 8> params;
+                for (auto* param : fn->Parameters()) {
+                    if (auto incoming_shape = variant_sig.Find(param)) {
+                        auto& symbols = *variant.ptr_param_symbols.Find(param);
+                        if (symbols.base_ptr.IsValid()) {
+                            auto* base_ptr_ty =
+                                b.ty.pointer(CreateASTTypeFor(ctx, incoming_shape->root.type),
+                                             incoming_shape->root.address_space);
+                            params.Push(b.Param(symbols.base_ptr, base_ptr_ty));
+                        }
+                        if (symbols.indices.IsValid()) {
+                            // Variant has dynamic indices for this variant, replace it.
+                            auto* dyn_idx_arr_type = DynamicIndexArrayType(*incoming_shape);
+                            params.Push(b.Param(symbols.indices, dyn_idx_arr_type));
+                        }
+                    } else {
+                        // Just a regular parameter. Just clone the original parameter.
+                        params.Push(ctx.Clone(param->Declaration()));
+                    }
+                }
+
+                // Build the variant by cloning the source function. The other clone callbacks will
+                // use clone_state->current_variant and clone_state->current_variant_sig to produce
+                // the variant.
+                auto* ret_ty = ctx.Clone(fn->Declaration()->return_type);
+                auto body = ctx.Clone(fn->Declaration()->body);
+                auto attrs = ctx.Clone(fn->Declaration()->attributes);
+                auto ret_attrs = ctx.Clone(fn->Declaration()->return_type_attributes);
+                pending_variant =
+                    b.create<ast::Function>(variant.name, std::move(params), ret_ty, body,
+                                            std::move(attrs), std::move(ret_attrs));
+            }
+
+            return pending_variant;
+        });
+    }
+
+    /// TransformCall registers the clone callback to transform the call expression @p call to call
+    /// the correct target variant, and to replace pointers arguments with an array of dynamic
+    /// indices.
+    void TransformCall(const sem::Call* call) {
+        // Register a custom handler for the specific call expression
+        ctx.Replace(call->Declaration(), [this, call]() {
+            auto target_variant = clone_state->current_variant->calls.Find(call);
+            if (!target_variant) {
+                // The current variant does not need to transform this call.
+                return ctx.CloneWithoutTransform(call->Declaration());
+            }
+
+            // Build the new call expressions's arguments.
+            utils::Vector<const ast::Expression*, 8> new_args;
+            for (size_t arg_idx = 0; arg_idx < call->Arguments().Length(); arg_idx++) {
+                auto* arg = call->Arguments()[arg_idx];
+                auto* param = call->Target()->Parameters()[arg_idx];
+                auto* param_ty = param->Type()->As<sem::Pointer>();
+                if (!param_ty) {
+                    // Parameter is not a pointer.
+                    // Just clone the unaltered argument.
+                    new_args.Push(ctx.Clone(arg->Declaration()));
+                    continue;  // Parameter is not a pointer
+                }
+
+                auto* chain = AccessChainFor(arg);
+                if (!chain) {
+                    // No access chain means the argument is not a pointer that needs transforming.
+                    // Just clone the unaltered argument.
+                    new_args.Push(ctx.Clone(arg->Declaration()));
+                    continue;
+                }
+
+                // Construct the absolute AccessShape by considering the AccessShape of the caller
+                // variant's argument. This will propagate back through pointer parameters, to the
+                // outermost caller.
+                auto full_indices = AbsoluteAccessShape(*clone_state->current_variant_sig, *chain);
+
+                // If the parameter is a pointer in the 'private' or 'function' address space, then
+                // we need to pass an additional pointer argument to the base object.
+                if (IsPrivateOrFunction(param_ty->AddressSpace())) {
+                    auto* root_expr = BuildAccessRootExpr(chain->root, /* deref */ false);
+                    if (!chain->root.variable->Is<sem::Parameter>()) {
+                        root_expr = b.AddressOf(root_expr);
+                    }
+                    new_args.Push(root_expr);
+                }
+
+                // Get or create the dynamic indices array.
+                if (auto* dyn_idx_arr_ty = DynamicIndexArrayType(full_indices)) {
+                    // Build an array of dynamic indices to pass as the replacement for the pointer.
+                    utils::Vector<const ast::Expression*, 8> dyn_idx_args;
+                    if (auto* root_param = chain->root.variable->As<sem::Parameter>()) {
+                        // Access chain originates from a pointer parameter.
+                        if (auto incoming_chain =
+                                clone_state->current_variant_sig->Find(root_param)) {
+                            auto indices =
+                                clone_state->current_variant->ptr_param_symbols.Find(root_param)
+                                    ->indices;
+
+                            // This pointer parameter will have been replaced with a array<u32, N>
+                            // holding the variant's dynamic indices for the pointer. Unpack these
+                            // directly into the array constructor's arguments.
+                            auto N = incoming_chain->NumDynamicIndices();
+                            for (uint32_t i = 0; i < N; i++) {
+                                dyn_idx_args.Push(b.IndexAccessor(indices, u32(i)));
+                            }
+                        }
+                    }
+                    // Pass the dynamic indices of the access chain into the array constructor.
+                    for (auto& dyn_idx : chain->dynamic_indices) {
+                        dyn_idx_args.Push(BuildDynamicIndex(dyn_idx, /* cast_to_u32 */ true));
+                    }
+                    // Construct the dynamic index array, and push as an argument.
+                    new_args.Push(b.Construct(dyn_idx_arr_ty, std::move(dyn_idx_args)));
+                }
+            }
+
+            // Make the call to the target's variant.
+            return b.Call(*target_variant, std::move(new_args));
+        });
+    }
+
+    /// ProcessAccessChains performs the following:
+    /// * Removes all AccessChains from expressions that are not either used as a pointer argument
+    ///   in a call, or originates from a pointer parameter.
+    /// * Hoists the dynamic index expressions of AccessChains to 'let' statements, to prevent
+    ///   multiple evaluation of the expressions, and avoid expressions resolving to different
+    ///   variables based on lexical scope.
+    void ProcessAccessChains() {
+        auto chain_exprs = access_chains.Keys();
+        chain_exprs.Sort([](const auto& expr_a, const auto& expr_b) {
+            return expr_a->Declaration()->node_id.value < expr_b->Declaration()->node_id.value;
+        });
+
+        for (auto* expr : chain_exprs) {
+            auto* chain = *access_chains.Get(expr);
+            if (!chain->used_in_call && !chain->root.variable->Is<sem::Parameter>()) {
+                // Chain was not used in a function call, and does not originate from a
+                // parameter. This chain does not need transforming. Drop it.
+                access_chains.Remove(expr);
+                continue;
+            }
+
+            // Chain requires transforming.
+
+            // We need to be careful that the chain does not use expressions with side-effects which
+            // cannot be repeatedly evaluated. In this situation we can hoist the dynamic index
+            // expressions to their own uniquely named lets (if required).
+            MaybeHoistDynamicIndices(chain, expr->Stmt());
+        }
+    }
+
+    /// TransformAccessChainExpressions registers the clone callback to:
+    /// * Transform all expressions that have an AccessChain (which aren't arguments to function
+    ///   calls, these are handled by TransformCall()), into the equivalent expression using a
+    ///   module-scope variable.
+    /// * Replace expressions that have been hoisted to a let, with an identifier expression to that
+    ///   let.
+    void TransformAccessChainExpressions() {
+        // Register a custom handler for all non-function call expressions
+        ctx.ReplaceAll([this](const ast::Expression* ast_expr) -> const ast::Expression* {
+            if (!clone_state->current_variant) {
+                // Expression does not belong to a function variant.
+                return nullptr;  // Just clone the expression.
+            }
+
+            auto* expr = sem.Get<sem::Expression>(ast_expr);
+            if (!expr) {
+                // No semantic node for the expression.
+                return nullptr;  // Just clone the expression.
+            }
+
+            // If the expression has been hoisted to a 'let', then replace the expression with an
+            // identifier to the hoisted let.
+            if (auto hoisted = clone_state->current_function->hoisted_exprs.Find(expr)) {
+                return b.Expr(*hoisted);
+            }
+
+            auto* chain = AccessChainFor(expr);
+            if (!chain) {
+                // The expression does not have an AccessChain.
+                return nullptr;  // Just clone the expression.
+            }
+
+            auto* root_param = chain->root.variable->As<sem::Parameter>();
+            if (!root_param) {
+                // The expression has an access chain, but does not originate with a pointer
+                // parameter. We don't need to change anything here.
+                return nullptr;  // Just clone the expression.
+            }
+
+            auto incoming_shape = clone_state->current_variant_sig->Find(root_param);
+            if (!incoming_shape) {
+                // The root parameter of the access chain is not part of the variant's signature.
+                return nullptr;  // Just clone the expression.
+            }
+
+            // Expression holds an access chain to a pointer parameter that needs transforming.
+            // Reconstruct the expression using the variant's incoming shape.
+
+            auto* chain_expr = BuildAccessRootExpr(incoming_shape->root, /* deref */ true);
+
+            // Chain starts with a pointer parameter.
+            // Replace this with the variant's incoming shape. This will bring the expression up to
+            // the incoming pointer.
+            auto indices =
+                clone_state->current_variant->ptr_param_symbols.Find(root_param)->indices;
+            for (auto param_access : incoming_shape->ops) {
+                chain_expr = BuildAccessExpr(chain_expr, param_access, [&](size_t i) {
+                    return b.IndexAccessor(indices, AInt(i));
+                });
+            }
+
+            // Now build the expression chain within the function.
+
+            // For each access in the chain (excluding the pointer parameter)...
+            for (auto& op : chain->ops) {
+                chain_expr = BuildAccessExpr(chain_expr, op, [&](size_t i) {
+                    return BuildDynamicIndex(chain->dynamic_indices[i], false);
+                });
+            }
+
+            // BuildAccessExpr() always returns a non-pointer.
+            // If the expression we're replacing is a pointer, take the address.
+            if (expr->Type()->Is<sem::Pointer>()) {
+                chain_expr = b.AddressOf(chain_expr);
+            }
+
+            return chain_expr;
+        });
+    }
+
+    /// @returns the FnInfo for the given function, constructing a new FnInfo if @p fn doesn't
+    /// already have one.
+    FnInfo* FnInfoFor(const sem::Function* fn) {
+        return fns.GetOrCreate(fn, [this] { return fn_info_allocator.Create(); });
+    }
+
+    /// @returns the type alias used to hold the dynamic indices for @p shape, declaring a new alias
+    /// if this is the first call for the given shape.
+    const ast::TypeName* DynamicIndexArrayType(const AccessShape& shape) {
+        auto name = dynamic_index_array_aliases.GetOrCreate(shape, [&] {
+            // Count the number of dynamic indices
+            uint32_t num_dyn_indices = shape.NumDynamicIndices();
+            if (num_dyn_indices == 0) {
+                return Symbol{};
+            }
+            auto symbol = b.Symbols().New(AccessShapeName(shape));
+            b.Alias(symbol, b.ty.array(b.ty.u32(), u32(num_dyn_indices)));
+            return symbol;
+        });
+        return name.IsValid() ? b.ty.type_name(name) : nullptr;
+    }
+
+    /// @returns a name describing the given shape
+    std::string AccessShapeName(const AccessShape& shape) {
+        std::stringstream ss;
+
+        if (IsPrivateOrFunction(shape.root.address_space)) {
+            ss << "F";
+        } else {
+            ss << ctx.src->Symbols().NameFor(shape.root.variable->Declaration()->symbol);
+        }
+
+        for (auto& op : shape.ops) {
+            ss << "_";
+
+            if (std::holds_alternative<DynamicIndex>(op)) {
+                /// The op uses a dynamic (runtime-expression) index.
+                ss << "X";
+                continue;
+            }
+
+            if (auto* member = std::get_if<Symbol>(&op)) {
+                ss << sym.NameFor(*member);
+                continue;
+            }
+
+            TINT_ICE(Transform, b.Diagnostics()) << "unhandled variant for access chain";
+            break;
+        }
+        return ss.str();
+    }
+
+    /// Builds an expresion to the root of an access, returning the new expression.
+    /// @param root the AccessRoot
+    /// @param deref if true, the returned expression will always be a reference type.
+    const ast::Expression* BuildAccessRootExpr(const AccessRoot& root, bool deref) {
+        if (auto* param = root.variable->As<sem::Parameter>()) {
+            if (auto symbols = clone_state->current_variant->ptr_param_symbols.Find(param)) {
+                if (deref) {
+                    return b.Deref(b.Expr(symbols->base_ptr));
+                }
+                return b.Expr(symbols->base_ptr);
+            }
+        }
+
+        const ast::Expression* expr = b.Expr(ctx.Clone(root.variable->Declaration()->symbol));
+        if (deref) {
+            if (root.variable->Type()->Is<sem::Pointer>()) {
+                expr = b.Deref(expr);
+            }
+        }
+        return expr;
+    }
+
+    /// Builds a single access in an access chain, returning the new expression.
+    /// The returned expression will always be of a reference type.
+    /// @param expr the input expression
+    /// @param access the access to perform on the current expression
+    /// @param dynamic_index a function that obtains the i'th dynamic index
+    const ast::Expression* BuildAccessExpr(
+        const ast::Expression* expr,
+        const AccessOp& access,
+        std::function<const ast::Expression*(size_t)> dynamic_index) {
+        if (auto* dyn_idx = std::get_if<DynamicIndex>(&access)) {
+            /// The access uses a dynamic (runtime-expression) index.
+            auto* idx = dynamic_index(dyn_idx->slot);
+            return b.IndexAccessor(expr, idx);
+        }
+
+        if (auto* member = std::get_if<Symbol>(&access)) {
+            /// The access is a member access.
+            return b.MemberAccessor(expr, ctx.Clone(*member));
+        }
+
+        TINT_ICE(Transform, b.Diagnostics()) << "unhandled variant type for access chain";
+        return nullptr;
+    }
+
+    /// @returns a new Symbol starting with @p symbol concatenated with @p suffix, and possibly an
+    /// underscore and number, if the symbol is already taken.
+    Symbol UniqueSymbolWithSuffix(Symbol symbol, const std::string& suffix) {
+        auto str = ctx.src->Symbols().NameFor(symbol) + suffix;
+        return unique_symbols.GetOrCreate(str, [&] { return b.Symbols().New(str); });
+    }
+
+    /// @returns true if the function @p fn has at least one pointer parameter.
+    static bool HasPointerParameter(const sem::Function* fn) {
+        for (auto* param : fn->Parameters()) {
+            if (param->Type()->Is<sem::Pointer>()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /// @returns true if the function @p fn has at least one pointer parameter in an address space
+    /// that must be replaced. If this function is not called, then the function cannot be sensibly
+    /// generated, and must be stripped.
+    static bool MustBeCalled(const sem::Function* fn) {
+        for (auto* param : fn->Parameters()) {
+            if (auto* ptr = param->Type()->As<sem::Pointer>()) {
+                switch (ptr->AddressSpace()) {
+                    case ast::AddressSpace::kUniform:
+                    case ast::AddressSpace::kStorage:
+                    case ast::AddressSpace::kWorkgroup:
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+        }
+        return false;
+    }
+
+    /// @returns true if the given address space is 'private' or 'function'.
+    static bool IsPrivateOrFunction(const ast::AddressSpace sc) {
+        return sc == ast::AddressSpace::kPrivate || sc == ast::AddressSpace::kFunction;
+    }
+};
+
+DirectVariableAccess::Config::Config(const Options& opt) : options(opt) {}
+
+DirectVariableAccess::Config::~Config() = default;
+
+DirectVariableAccess::DirectVariableAccess() = default;
+
+DirectVariableAccess::~DirectVariableAccess() = default;
+
+Transform::ApplyResult DirectVariableAccess::Apply(const Program* program,
+                                                   const DataMap& inputs,
+                                                   DataMap&) const {
+    Options options;
+    if (auto* cfg = inputs.Get<Config>()) {
+        options = cfg->options;
+    }
+    return State(program, options).Run();
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/direct_variable_access.h b/src/tint/transform/direct_variable_access.h
new file mode 100644
index 0000000..34b6e91
--- /dev/null
+++ b/src/tint/transform/direct_variable_access.h
@@ -0,0 +1,74 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
+#define SRC_TINT_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// DirectVariableAccess is a transform that allows usage of pointer parameters in the 'storage',
+/// 'uniform' and 'workgroup' address space, and passing of pointers to sub-objects. These pointers
+/// are only allowed by the resolver when the `chromium_experimental_full_ptr_parameters` extension
+/// is enabled.
+///
+/// DirectVariableAccess works by creating specializations of functions that have pointer
+/// parameters, one specialization for each pointer argument's unique access chain 'shape' from a
+/// unique variable. Calls to specialized functions are transformed so that the pointer arguments
+/// are replaced with an array of access-chain indicies, and if the pointer is in the 'function' or
+/// 'private' address space, also with a pointer to the root object. For more information, see the
+/// comments in src/tint/transform/direct_variable_access.cc.
+///
+/// @note DirectVariableAccess requires the transform::Unshadow transform to have been run first.
+class DirectVariableAccess final : public Castable<DirectVariableAccess, Transform> {
+  public:
+    /// Constructor
+    DirectVariableAccess();
+    /// Destructor
+    ~DirectVariableAccess() override;
+
+    /// Options adjusts the behaviour of the transform.
+    struct Options {
+        /// If true, then 'private' sub-object pointer arguments will be transformed.
+        bool transform_private = false;
+        /// If true, then 'function' sub-object pointer arguments will be transformed.
+        bool transform_function = false;
+    };
+
+    /// Config is consumed by the DirectVariableAccess transform.
+    /// Config specifies the behavior of the transform.
+    struct Config final : public Castable<Data, transform::Data> {
+        /// Constructor
+        /// @param options behavior of the transform
+        explicit Config(const Options& options);
+        /// Destructor
+        ~Config() override;
+
+        /// The transform behavior options
+        const Options options;
+    };
+
+    /// @copydoc Transform::Apply
+    ApplyResult Apply(const Program* program,
+                      const DataMap& inputs,
+                      DataMap& outputs) const override;
+
+  private:
+    struct State;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
diff --git a/src/tint/transform/direct_variable_access_test.cc b/src/tint/transform/direct_variable_access_test.cc
new file mode 100644
index 0000000..4c112d4
--- /dev/null
+++ b/src/tint/transform/direct_variable_access_test.cc
@@ -0,0 +1,2697 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/transform/direct_variable_access.h"
+
+#include <memory>
+#include <utility>
+
+#include "src/tint/transform/test_helper.h"
+#include "src/tint/utils/string.h"
+
+namespace tint::transform {
+namespace {
+
+/// @returns a DataMap with DirectVariableAccess::Config::transform_private enabled.
+static DataMap EnablePrivate() {
+    DirectVariableAccess::Options opts;
+    opts.transform_private = true;
+
+    DataMap inputs;
+    inputs.Add<DirectVariableAccess::Config>(opts);
+    return inputs;
+}
+
+/// @returns a DataMap with DirectVariableAccess::Config::transform_function enabled.
+static DataMap EnableFunction() {
+    DirectVariableAccess::Options opts;
+    opts.transform_function = true;
+
+    DataMap inputs;
+    inputs.Add<DirectVariableAccess::Config>(opts);
+    return inputs;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ShouldRun tests
+////////////////////////////////////////////////////////////////////////////////
+namespace should_run {
+
+using DirectVariableAccessShouldRunTest = TransformTest;
+
+TEST_F(DirectVariableAccessShouldRunTest, EmptyModule) {
+    auto* src = R"()";
+
+    EXPECT_FALSE(ShouldRun<DirectVariableAccess>(src));
+}
+
+}  // namespace should_run
+
+////////////////////////////////////////////////////////////////////////////////
+// remove uncalled
+////////////////////////////////////////////////////////////////////////////////
+namespace remove_uncalled {
+
+using DirectVariableAccessRemoveUncalledTest = TransformTest;
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrUniform) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn u(pre : i32, p : ptr<uniform, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrStorage) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn s(pre : i32, p : ptr<storage, i32>, post : i32) -> i32 {
+  return *(p);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrWorkgroup) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn w(pre : i32, p : ptr<workgroup, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrPrivate_Disabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn f(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrPrivate_Enabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrFunction_Disabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+
+fn f(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessRemoveUncalledTest, PtrFunction_Enabled) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<private> keep_me = 42;
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace remove_uncalled
+
+////////////////////////////////////////////////////////////////////////////////
+// pointer chains
+////////////////////////////////////////////////////////////////////////////////
+namespace pointer_chains_tests {
+
+using DirectVariableAccessPtrChainsTest = TransformTest;
+
+TEST_F(DirectVariableAccessPtrChainsTest, ConstantIndices) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p0 = &U;
+  let p1 = &(*p0)[1];
+  let p2 = &(*p1)[1+1];
+  let p3 = &(*p2)[2*2 - 1];
+  a(10, p3, 20);
+}
+
+fn c(p : ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>) {
+  let p0 = p;
+  let p1 = &(*p0)[1];
+  let p2 = &(*p1)[1+1];
+  let p3 = &(*p2)[2*2 - 1];
+  a(10, p3, 20);
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+type U_X_X_X = array<u32, 3u>;
+
+fn a_U_X_X_X(pre : i32, p : U_X_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]][p[2]];
+}
+
+fn b() {
+  let p0 = &(U);
+  let p1 = &((*(p0))[1]);
+  let p2 = &((*(p1))[(1 + 1)]);
+  let p3 = &((*(p2))[((2 * 2) - 1)]);
+  a_U_X_X_X(10, U_X_X_X(1, 2, 3), 20);
+}
+
+fn c_U() {
+  let p0 = &(U);
+  let p1 = &(U[1]);
+  let p2 = &(U[1][2]);
+  let p3 = &(U[1][2][3]);
+  a_U_X_X_X(10, U_X_X_X(1, 2, 3), 20);
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistIndices) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+fn third() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p0 = &U;
+  let p1 = &(*p0)[first()];
+  let p2 = &(*p1)[second()][third()];
+  a(10, p2, 20);
+}
+
+fn c(p : ptr<uniform, array<array<array<vec4<i32>, 8>, 8>, 8>>) {
+  let p0 = &U;
+  let p1 = &(*p0)[first()];
+  let p2 = &(*p1)[second()][third()];
+  a(10, p2, 20);
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 8>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn third() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X_X = array<u32, 3u>;
+
+fn a_U_X_X_X(pre : i32, p : U_X_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]][p[2]];
+}
+
+fn b() {
+  let p0 = &(U);
+  let ptr_index_save = first();
+  let p1 = &((*(p0))[ptr_index_save]);
+  let ptr_index_save_1 = second();
+  let ptr_index_save_2 = third();
+  let p2 = &((*(p1))[ptr_index_save_1][ptr_index_save_2]);
+  a_U_X_X_X(10, U_X_X_X(u32(ptr_index_save), u32(ptr_index_save_1), u32(ptr_index_save_2)), 20);
+}
+
+fn c_U() {
+  let p0 = &(U);
+  let ptr_index_save_3 = first();
+  let p1 = &((*(p0))[ptr_index_save_3]);
+  let ptr_index_save_4 = second();
+  let ptr_index_save_5 = third();
+  let p2 = &((*(p1))[ptr_index_save_4][ptr_index_save_5]);
+  a_U_X_X_X(10, U_X_X_X(u32(ptr_index_save_3), u32(ptr_index_save_4), u32(ptr_index_save_5)), 20);
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInForLoopInit) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  for (let p1 = &U[first()]; true; ) {
+    a(10, &(*p1)[second()], 20);
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  for (let p1 = &(*p)[first()]; true; ) {
+    a(10, &(*p1)[second()], 20);
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  for(let p1 = &(U[ptr_index_save]); true; ) {
+    a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(second())), 20);
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_1 = first();
+  for(let p1 = &(U[ptr_index_save_1]); true; ) {
+    a_U_X_X(10, U_X_X(u32(ptr_index_save_1), u32(second())), 20);
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInForLoopCond) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p = &U[first()][second()];
+  for (; a(10, p, 20).x < 4; ) {
+    let body = 1;
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  let p2 = &(*p)[first()][second()];
+  for (; a(10, p2, 20).x < 4; ) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  let ptr_index_save_1 = second();
+  let p = &(U[ptr_index_save][ptr_index_save_1]);
+  for(; (a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(ptr_index_save_1)), 20).x < 4); ) {
+    let body = 1;
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_2 = first();
+  let ptr_index_save_3 = second();
+  let p2 = &(U[ptr_index_save_2][ptr_index_save_3]);
+  for(; (a_U_X_X(10, U_X_X(u32(ptr_index_save_2), u32(ptr_index_save_3)), 20).x < 4); ) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInForLoopCont) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p = &U[first()][second()];
+  for (var i = 0; i < 3; a(10, p, 20)) {
+    i++;
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  let p2 = &(*p)[first()][second()];
+  for (var i = 0; i < 3; a(10, p2, 20)) {
+    i++;
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  let ptr_index_save_1 = second();
+  let p = &(U[ptr_index_save][ptr_index_save_1]);
+  for(var i = 0; (i < 3); a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(ptr_index_save_1)), 20)) {
+    i++;
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_2 = first();
+  let ptr_index_save_3 = second();
+  let p2 = &(U[ptr_index_save_2][ptr_index_save_3]);
+  for(var i = 0; (i < 3); a_U_X_X(10, U_X_X(u32(ptr_index_save_2), u32(ptr_index_save_3)), 20)) {
+    i++;
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPtrChainsTest, HoistInWhileCond) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+fn first() -> i32 {
+  i++;
+  return i;
+}
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let p = &U[first()][second()];
+  while (a(10, p, 20).x < 4) {
+    let body = 1;
+  }
+}
+
+fn c(p : ptr<uniform, array<array<vec4<i32>, 8>, 8>>) {
+  let p2 = &(*p)[first()][second()];
+  while (a(10, p2, 20).x < 4) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<array<vec4<i32>, 8>, 8>;
+
+var<private> i : i32;
+
+fn first() -> i32 {
+  i++;
+  return i;
+}
+
+fn second() -> i32 {
+  i++;
+  return i;
+}
+
+type U_X_X = array<u32, 2u>;
+
+fn a_U_X_X(pre : i32, p : U_X_X, post : i32) -> vec4<i32> {
+  return U[p[0]][p[1]];
+}
+
+fn b() {
+  let ptr_index_save = first();
+  let ptr_index_save_1 = second();
+  let p = &(U[ptr_index_save][ptr_index_save_1]);
+  while((a_U_X_X(10, U_X_X(u32(ptr_index_save), u32(ptr_index_save_1)), 20).x < 4)) {
+    let body = 1;
+  }
+}
+
+fn c_U() {
+  let ptr_index_save_2 = first();
+  let ptr_index_save_3 = second();
+  let p2 = &(U[ptr_index_save_2][ptr_index_save_3]);
+  while((a_U_X_X(10, U_X_X(u32(ptr_index_save_2), u32(ptr_index_save_3)), 20).x < 4)) {
+    let body = 1;
+  }
+}
+
+fn d() {
+  c_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace pointer_chains_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'uniform' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace uniform_as_tests {
+
+using DirectVariableAccessUniformASTest = TransformTest;
+
+TEST_F(DirectVariableAccessUniformASTest, Param_ptr_i32_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : i32;
+
+fn a(pre : i32, p : ptr<uniform, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  a(10, &U, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : i32;
+
+fn a_U(pre : i32, post : i32) -> i32 {
+  return U;
+}
+
+fn b() {
+  a_U(10, 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessUniformASTest, Param_ptr_vec4i32_Via_array_DynamicRead) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<uniform, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let I = 3;
+  a(10, &U[I], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> U : array<vec4<i32>, 8>;
+
+type U_X = array<u32, 1u>;
+
+fn a_U_X(pre : i32, p : U_X, post : i32) -> vec4<i32> {
+  return U[p[0]];
+}
+
+fn b() {
+  let I = 3;
+  a_U_X(10, U_X(u32(I)), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessUniformASTest, CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+fn f0(p : ptr<uniform, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<uniform, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of U
+    res += f0(&U.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of U
+    let p_vec = &U.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<uniform, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<uniform, InnerArr>, p1 : ptr<uniform, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<uniform, Outer>) -> f32 {
+  return f3(&(*p).arr, &U.mat);
+}
+
+fn b() {
+  f4(&U);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<uniform> U : Outer;
+
+type U_mat_X = array<u32, 1u>;
+
+fn f0_U_mat_X(p : U_mat_X) -> f32 {
+  return U.mat[p[0]].x;
+}
+
+type U_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_U_arr_X_mat_X(p : U_arr_X_mat_X) -> f32 {
+  return U.arr[p[0]].mat[p[0]].x;
+}
+
+type U_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_U_arr_X_mat_X_1(p : U_arr_X_mat_X_1) -> f32 {
+  return U.arr[p[0]].mat[p[1]].x;
+}
+
+fn f1_U_mat() -> f32 {
+  var res : f32;
+  {
+    res += f0_U_mat_X(U_mat_X(1));
+  }
+  {
+    let p_vec = &(U.mat[1]);
+    res += f0_U_mat_X(U_mat_X(1));
+  }
+  {
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(U.arr[2].mat[1]);
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type U_arr_X_mat = array<u32, 1u>;
+
+fn f1_U_arr_X_mat(p : U_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    let p_vec = &(U.arr[p[0]].mat[1]);
+    res += f0_U_arr_X_mat_X(U_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(U.arr[2].mat[1]);
+    res += f0_U_arr_X_mat_X_1(U_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type U_arr_X = array<u32, 1u>;
+
+fn f2_U_arr_X(p : U_arr_X) -> f32 {
+  let p_mat = &(U.arr[p[0]].mat);
+  return f1_U_arr_X_mat(U_arr_X_mat(p[0u]));
+}
+
+fn f3_U_arr_U_mat() -> f32 {
+  let p0_inner = &(U.arr[3]);
+  return (f2_U_arr_X(U_arr_X(3)) + f1_U_mat());
+}
+
+fn f4_U() -> f32 {
+  return f3_U_arr_U_mat();
+}
+
+fn b() {
+  f4_U();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace uniform_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'storage' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace storage_as_tests {
+
+using DirectVariableAccessStorageASTest = TransformTest;
+
+TEST_F(DirectVariableAccessStorageASTest, Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn a(pre : i32, p : ptr<storage, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  a(10, &S.i, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn a_S_i(pre : i32, post : i32) -> i32 {
+  return S.i;
+}
+
+fn b() {
+  a_S_i(10, 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessStorageASTest, Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn a(pre : i32, p : ptr<storage, array<i32, 4>, read_write>, post : i32) {
+  *p = array<i32, 4>();
+}
+
+fn b() {
+  a(10, &S.arr, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn a_S_arr(pre : i32, post : i32) {
+  S.arr = array<i32, 4>();
+}
+
+fn b() {
+  a_S_arr(10, 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessStorageASTest, Param_ptr_vec4i32_Via_array_DynamicWrite) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<storage, vec4<i32>, read_write>, post : i32) {
+  *p = vec4<i32>();
+}
+
+fn b() {
+  let I = 3;
+  a(10, &S[I], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : array<vec4<i32>, 8>;
+
+type S_X = array<u32, 1u>;
+
+fn a_S_X(pre : i32, p : S_X, post : i32) {
+  S[p[0]] = vec4<i32>();
+}
+
+fn b() {
+  let I = 3;
+  a_S_X(10, S_X(u32(I)), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessStorageASTest, CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+@group(0) @binding(0) var<storage> S : Outer;
+
+fn f0(p : ptr<storage, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<storage, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of S
+    res += f0(&S.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of S
+    let p_vec = &S.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<storage, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<storage, InnerArr>, p1 : ptr<storage, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<storage, Outer>) -> f32 {
+  return f3(&(*p).arr, &S.mat);
+}
+
+fn b() {
+  f4(&S);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+@group(0) @binding(0) var<storage> S : Outer;
+
+type S_mat_X = array<u32, 1u>;
+
+fn f0_S_mat_X(p : S_mat_X) -> f32 {
+  return S.mat[p[0]].x;
+}
+
+type S_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_S_arr_X_mat_X(p : S_arr_X_mat_X) -> f32 {
+  return S.arr[p[0]].mat[p[0]].x;
+}
+
+type S_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_S_arr_X_mat_X_1(p : S_arr_X_mat_X_1) -> f32 {
+  return S.arr[p[0]].mat[p[1]].x;
+}
+
+fn f1_S_mat() -> f32 {
+  var res : f32;
+  {
+    res += f0_S_mat_X(S_mat_X(1));
+  }
+  {
+    let p_vec = &(S.mat[1]);
+    res += f0_S_mat_X(S_mat_X(1));
+  }
+  {
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(S.arr[2].mat[1]);
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type S_arr_X_mat = array<u32, 1u>;
+
+fn f1_S_arr_X_mat(p : S_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    let p_vec = &(S.arr[p[0]].mat[1]);
+    res += f0_S_arr_X_mat_X(S_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(S.arr[2].mat[1]);
+    res += f0_S_arr_X_mat_X_1(S_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type S_arr_X = array<u32, 1u>;
+
+fn f2_S_arr_X(p : S_arr_X) -> f32 {
+  let p_mat = &(S.arr[p[0]].mat);
+  return f1_S_arr_X_mat(S_arr_X_mat(p[0u]));
+}
+
+fn f3_S_arr_S_mat() -> f32 {
+  let p0_inner = &(S.arr[3]);
+  return (f2_S_arr_X(S_arr_X(3)) + f1_S_mat());
+}
+
+fn f4_S() -> f32 {
+  return f3_S_arr_S_mat();
+}
+
+fn b() {
+  f4_S();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace storage_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'workgroup' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace workgroup_as_tests {
+
+using DirectVariableAccessWorkgroupASTest = TransformTest;
+
+TEST_F(DirectVariableAccessWorkgroupASTest, Param_ptr_vec4i32_Via_array_StaticRead) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<workgroup, vec4<i32>>, post : i32) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  a(10, &W[3], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+type W_X = array<u32, 1u>;
+
+fn a_W_X(pre : i32, p : W_X, post : i32) -> vec4<i32> {
+  return W[p[0]];
+}
+
+fn b() {
+  a_W_X(10, W_X(3), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessWorkgroupASTest, Param_ptr_vec4i32_Via_array_StaticWrite) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+fn a(pre : i32, p : ptr<workgroup, vec4<i32>>, post : i32) {
+  *p = vec4<i32>();
+}
+
+fn b() {
+  a(10, &W[3], 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> W : array<vec4<i32>, 8>;
+
+type W_X = array<u32, 1u>;
+
+fn a_W_X(pre : i32, p : W_X, post : i32) {
+  W[p[0]] = vec4<i32>();
+}
+
+fn b() {
+  a_W_X(10, W_X(3), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessWorkgroupASTest, CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+var<workgroup> W : Outer;
+
+fn f0(p : ptr<workgroup, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<workgroup, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of W
+    res += f0(&W.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of W
+    let p_vec = &W.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<workgroup, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<workgroup, InnerArr>, p1 : ptr<workgroup, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<workgroup, Outer>) -> f32 {
+  return f3(&(*p).arr, &W.mat);
+}
+
+fn b() {
+  f4(&W);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+var<workgroup> W : Outer;
+
+type W_mat_X = array<u32, 1u>;
+
+fn f0_W_mat_X(p : W_mat_X) -> f32 {
+  return W.mat[p[0]].x;
+}
+
+type W_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_W_arr_X_mat_X(p : W_arr_X_mat_X) -> f32 {
+  return W.arr[p[0]].mat[p[0]].x;
+}
+
+type W_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_W_arr_X_mat_X_1(p : W_arr_X_mat_X_1) -> f32 {
+  return W.arr[p[0]].mat[p[1]].x;
+}
+
+fn f1_W_mat() -> f32 {
+  var res : f32;
+  {
+    res += f0_W_mat_X(W_mat_X(1));
+  }
+  {
+    let p_vec = &(W.mat[1]);
+    res += f0_W_mat_X(W_mat_X(1));
+  }
+  {
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(W.arr[2].mat[1]);
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type W_arr_X_mat = array<u32, 1u>;
+
+fn f1_W_arr_X_mat(p : W_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    let p_vec = &(W.arr[p[0]].mat[1]);
+    res += f0_W_arr_X_mat_X(W_arr_X_mat_X(p[0u], 1));
+  }
+  {
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  {
+    let p_vec = &(W.arr[2].mat[1]);
+    res += f0_W_arr_X_mat_X_1(W_arr_X_mat_X_1(2, 1));
+  }
+  return res;
+}
+
+type W_arr_X = array<u32, 1u>;
+
+fn f2_W_arr_X(p : W_arr_X) -> f32 {
+  let p_mat = &(W.arr[p[0]].mat);
+  return f1_W_arr_X_mat(W_arr_X_mat(p[0u]));
+}
+
+fn f3_W_arr_W_mat() -> f32 {
+  let p0_inner = &(W.arr[3]);
+  return (f2_W_arr_X(W_arr_X(3)) + f1_W_mat());
+}
+
+fn f4_W() -> f32 {
+  return f3_W_arr_W_mat();
+}
+
+fn b() {
+  f4_W();
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace workgroup_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'private' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace private_as_tests {
+
+using DirectVariableAccessPrivateASTest = TransformTest;
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> P : i32;
+
+fn b() {
+  a(10, &(P), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> P : i32;
+
+fn b() {
+  a_F(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) {
+  *(p) = 42;
+}
+
+var<private> P : i32;
+
+fn b() {
+  a(10, &(P), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<private, i32>, post : i32) {
+  *(p) = 42;
+}
+
+var<private> P : i32;
+
+fn b() {
+  a_F(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &P.i, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F_i(pre : i32, p : ptr<private, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+var<private> P : str;
+
+fn b() {
+  a_F_i(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &(P.i), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn a(pre : i32, p : ptr<private, array<i32, 4>>, post : i32) {
+  *p = array<i32, 4>();
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &P.arr, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a_F_arr(pre : i32, p : ptr<private, str>, post : i32) {
+  (*(p)).arr = array<i32, 4>();
+}
+
+var<private> P : str;
+
+fn b() {
+  a_F_arr(10, &(P), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a(pre : i32, p : ptr<private, array<i32, 4>>, post : i32) {
+  *(p) = array<i32, 4>();
+}
+
+var<private> P : str;
+
+fn b() {
+  a(10, &(P.arr), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_Param_ptr_i32_mixed) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+var<private> Pi : i32;
+var<private> Ps : str;
+var<private> Pa : array<i32, 4>;
+
+fn b() {
+  a(10, &Pi, 20);
+  a(30, &Ps.i, 40);
+  a(50, &Pa[2], 60);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn a_F_i(pre : i32, p : ptr<private, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+type F_X = array<u32, 1u>;
+
+fn a_F_X(pre : i32, p_base : ptr<private, array<i32, 4u>>, p_indices : F_X, post : i32) -> i32 {
+  return (*(p_base))[p_indices[0]];
+}
+
+var<private> Pi : i32;
+
+var<private> Ps : str;
+
+var<private> Pa : array<i32, 4>;
+
+type F_X_1 = array<u32, 1u>;
+
+fn b() {
+  a_F(10, &(Pi), 20);
+  a_F_i(30, &(Ps), 40);
+  a_F_X(50, &(Pa), F_X_1(2), 60);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_Param_ptr_i32_mixed) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a(pre : i32, p : ptr<private, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+var<private> Pi : i32;
+
+var<private> Ps : str;
+
+var<private> Pa : array<i32, 4>;
+
+fn b() {
+  a(10, &(Pi), 20);
+  a(10, &(Ps.i), 20);
+  a(10, &(Pa[2]), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Enabled_CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+};
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+};
+
+var<private> P : Outer;
+
+fn f0(p : ptr<private, vec4<f32>>) -> f32 {
+  return (*p).x;
+}
+
+fn f1(p : ptr<private, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    // call f0() with inline usage of p
+    res += f0(&(*p)[1]);
+  }
+  {
+    // call f0() with pointer-let usage of p
+    let p_vec = &(*p)[1];
+    res += f0(p_vec);
+  }
+  {
+    // call f0() with inline usage of P
+    res += f0(&P.arr[2].mat[1]);
+  }
+  {
+    // call f0() with pointer-let usage of P
+    let p_vec = &P.arr[2].mat[1];
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<private, Inner>) -> f32 {
+  let p_mat = &(*p).mat;
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<private, InnerArr>, p1 : ptr<private, mat3x4<f32>>) -> f32 {
+  let p0_inner = &(*p0)[3];
+  return f2(p0_inner) + f1(p1);
+}
+
+fn f4(p : ptr<private, Outer>) -> f32 {
+  return f3(&(*p).arr, &P.mat);
+}
+
+fn b() {
+  f4(&P);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+var<private> P : Outer;
+
+type F_mat_X = array<u32, 1u>;
+
+fn f0_F_mat_X(p_base : ptr<private, Outer>, p_indices : F_mat_X) -> f32 {
+  return (*(p_base)).mat[p_indices[0]].x;
+}
+
+type F_arr_X_mat_X = array<u32, 2u>;
+
+fn f0_F_arr_X_mat_X(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat_X) -> f32 {
+  return (*(p_base)).arr[p_indices[0]].mat[p_indices[0]].x;
+}
+
+type F_arr_X_mat_X_1 = array<u32, 2u>;
+
+fn f0_F_arr_X_mat_X_1(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat_X_1) -> f32 {
+  return (*(p_base)).arr[p_indices[0]].mat[p_indices[1]].x;
+}
+
+type F_mat_X_1 = array<u32, 1u>;
+
+type F_arr_X_mat_X_2 = array<u32, 2u>;
+
+fn f1_F_mat(p : ptr<private, Outer>) -> f32 {
+  var res : f32;
+  {
+    res += f0_F_mat_X(p, F_mat_X_1(1));
+  }
+  {
+    let p_vec = &((*(p)).mat[1]);
+    res += f0_F_mat_X(p, F_mat_X_1(1));
+  }
+  {
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  {
+    let p_vec = &(P.arr[2].mat[1]);
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  return res;
+}
+
+type F_arr_X_mat = array<u32, 1u>;
+
+type F_arr_X_mat_X_3 = array<u32, 2u>;
+
+fn f1_F_arr_X_mat(p_base : ptr<private, Outer>, p_indices : F_arr_X_mat) -> f32 {
+  var res : f32;
+  {
+    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_3(p_indices[0u], 1));
+  }
+  {
+    let p_vec = &((*(p_base)).arr[p_indices[0]].mat[1]);
+    res += f0_F_arr_X_mat_X(p_base, F_arr_X_mat_X_3(p_indices[0u], 1));
+  }
+  {
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  {
+    let p_vec = &(P.arr[2].mat[1]);
+    res += f0_F_arr_X_mat_X_1(&(P), F_arr_X_mat_X_2(2, 1));
+  }
+  return res;
+}
+
+type F_arr_X = array<u32, 1u>;
+
+type F_arr_X_mat_1 = array<u32, 1u>;
+
+fn f2_F_arr_X(p_base : ptr<private, Outer>, p_indices : F_arr_X) -> f32 {
+  let p_mat = &((*(p_base)).arr[p_indices[0]].mat);
+  return f1_F_arr_X_mat(p_base, F_arr_X_mat_1(p_indices[0u]));
+}
+
+type F_arr_X_1 = array<u32, 1u>;
+
+fn f3_F_arr_F_mat(p0 : ptr<private, Outer>, p1 : ptr<private, Outer>) -> f32 {
+  let p0_inner = &((*(p0)).arr[3]);
+  return (f2_F_arr_X(p0, F_arr_X_1(3)) + f1_F_mat(p1));
+}
+
+fn f4_F(p : ptr<private, Outer>) -> f32 {
+  return f3_F_arr_F_mat(p, &(P));
+}
+
+fn b() {
+  f4_F(&(P));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnablePrivate());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessPrivateASTest, Disabled_CallChaining) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct Inner {
+  mat : mat3x4<f32>,
+}
+
+type InnerArr = array<Inner, 4>;
+
+struct Outer {
+  arr : InnerArr,
+  mat : mat3x4<f32>,
+}
+
+var<private> P : Outer;
+
+fn f0(p : ptr<private, vec4<f32>>) -> f32 {
+  return (*(p)).x;
+}
+
+fn f1(p : ptr<private, mat3x4<f32>>) -> f32 {
+  var res : f32;
+  {
+    res += f0(&((*(p))[1]));
+  }
+  {
+    let p_vec = &((*(p))[1]);
+    res += f0(p_vec);
+  }
+  {
+    res += f0(&(P.arr[2].mat[1]));
+  }
+  {
+    let p_vec = &(P.arr[2].mat[1]);
+    res += f0(p_vec);
+  }
+  return res;
+}
+
+fn f2(p : ptr<private, Inner>) -> f32 {
+  let p_mat = &((*(p)).mat);
+  return f1(p_mat);
+}
+
+fn f3(p0 : ptr<private, InnerArr>, p1 : ptr<private, mat3x4<f32>>) -> f32 {
+  let p0_inner = &((*(p0))[3]);
+  return (f2(p0_inner) + f1(p1));
+}
+
+fn f4(p : ptr<private, Outer>) -> f32 {
+  return f3(&((*(p)).arr), &(P.mat));
+}
+
+fn b() {
+  f4(&(P));
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace private_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// 'function' address space
+////////////////////////////////////////////////////////////////////////////////
+namespace function_as_tests {
+
+using DirectVariableAccessFunctionASTest = TransformTest;
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_LocalPtr) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn f() {
+  var v : i32;
+  let p : ptr<function, i32> = &(v);
+  var x : i32 = *(p);
+}
+)";
+
+    auto* expect = src;  // Nothing changes
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn b() {
+  var F : i32;
+  a(10, &(F), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn b() {
+  var F : i32;
+  a_F(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) {
+  *(p) = 42;
+}
+
+fn b() {
+  var F : i32;
+  a(10, &(F), 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+fn a_F(pre : i32, p : ptr<function, i32>, post : i32) {
+  *(p) = 42;
+}
+
+fn b() {
+  var F : i32;
+  a_F(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  var F : str;
+  a(10, &F.i, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F_i(pre : i32, p : ptr<function, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+fn b() {
+  var F : str;
+  a_F_i(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn a(pre : i32, p : ptr<function, array<i32, 4>>, post : i32) {
+  *p = array<i32, 4>();
+}
+
+fn b() {
+  var F : str;
+  a(10, &F.arr, 20);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a_F_arr(pre : i32, p : ptr<function, str>, post : i32) {
+  (*(p)).arr = array<i32, 4>();
+}
+
+fn b() {
+  var F : str;
+  a_F_arr(10, &(F), 20);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Enabled_Param_ptr_i32_mixed) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *p;
+}
+
+fn b() {
+  var Fi : i32;
+  var Fs : str;
+  var Fa : array<i32, 4>;
+
+  a(10, &Fi, 20);
+  a(30, &Fs.i, 40);
+  a(50, &Fa[2], 60);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a_F(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn a_F_i(pre : i32, p : ptr<function, str>, post : i32) -> i32 {
+  return (*(p)).i;
+}
+
+type F_X = array<u32, 1u>;
+
+fn a_F_X(pre : i32, p_base : ptr<function, array<i32, 4u>>, p_indices : F_X, post : i32) -> i32 {
+  return (*(p_base))[p_indices[0]];
+}
+
+type F_X_1 = array<u32, 1u>;
+
+fn b() {
+  var Fi : i32;
+  var Fs : str;
+  var Fa : array<i32, 4>;
+  a_F(10, &(Fi), 20);
+  a_F_i(30, &(Fs), 40);
+  a_F_X(50, &(Fa), F_X_1(2), 60);
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src, EnableFunction());
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Disabled_Param_ptr_i32_Via_struct_read) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn a(pre : i32, p : ptr<function, i32>, post : i32) -> i32 {
+  return *(p);
+}
+
+fn b() {
+  var F : str;
+  a(10, &(F.i), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessFunctionASTest, Disabled_Param_ptr_arr_i32_Via_struct_write) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn a(pre : i32, p : ptr<function, array<i32, 4>>, post : i32) {
+  *(p) = array<i32, 4>();
+}
+
+fn b() {
+  var F : str;
+  a(10, &(F.arr), 20);
+}
+)";
+
+    auto* expect = src;
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace function_as_tests
+
+////////////////////////////////////////////////////////////////////////////////
+// complex tests
+////////////////////////////////////////////////////////////////////////////////
+namespace complex_tests {
+
+using DirectVariableAccessComplexTest = TransformTest;
+
+TEST_F(DirectVariableAccessComplexTest, Param_ptr_mixed_vec4i32_ViaMultiple) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<i32>,
+};
+
+@group(0) @binding(0) var<uniform> U     : vec4<i32>;
+@group(0) @binding(1) var<uniform> U_str   : str;
+@group(0) @binding(2) var<uniform> U_arr   : array<vec4<i32>, 8>;
+@group(0) @binding(3) var<uniform> U_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+@group(1) @binding(0) var<storage> S     : vec4<i32>;
+@group(1) @binding(1) var<storage> S_str   : str;
+@group(1) @binding(2) var<storage> S_arr   : array<vec4<i32>, 8>;
+@group(1) @binding(3) var<storage> S_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+          var<workgroup> W     : vec4<i32>;
+          var<workgroup> W_str   : str;
+          var<workgroup> W_arr   : array<vec4<i32>, 8>;
+          var<workgroup> W_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+fn fn_u(p : ptr<uniform, vec4<i32>>) -> vec4<i32> {
+  return *p;
+}
+
+fn fn_s(p : ptr<storage, vec4<i32>>) -> vec4<i32> {
+  return *p;
+}
+
+fn fn_w(p : ptr<workgroup, vec4<i32>>) -> vec4<i32> {
+  return *p;
+}
+
+fn b() {
+  let I = 3;
+  let J = 4;
+
+  let u           = fn_u(&U);
+  let u_str       = fn_u(&U_str.i);
+  let u_arr0      = fn_u(&U_arr[0]);
+  let u_arr1      = fn_u(&U_arr[1]);
+  let u_arrI      = fn_u(&U_arr[I]);
+  let u_arr1_arr0 = fn_u(&U_arr_arr[1][0]);
+  let u_arr2_arrI = fn_u(&U_arr_arr[2][I]);
+  let u_arrI_arr2 = fn_u(&U_arr_arr[I][2]);
+  let u_arrI_arrJ = fn_u(&U_arr_arr[I][J]);
+
+  let s           = fn_s(&S);
+  let s_str       = fn_s(&S_str.i);
+  let s_arr0      = fn_s(&S_arr[0]);
+  let s_arr1      = fn_s(&S_arr[1]);
+  let s_arrI      = fn_s(&S_arr[I]);
+  let s_arr1_arr0 = fn_s(&S_arr_arr[1][0]);
+  let s_arr2_arrI = fn_s(&S_arr_arr[2][I]);
+  let s_arrI_arr2 = fn_s(&S_arr_arr[I][2]);
+  let s_arrI_arrJ = fn_s(&S_arr_arr[I][J]);
+
+  let w           = fn_w(&W);
+  let w_str       = fn_w(&W_str.i);
+  let w_arr0      = fn_w(&W_arr[0]);
+  let w_arr1      = fn_w(&W_arr[1]);
+  let w_arrI      = fn_w(&W_arr[I]);
+  let w_arr1_arr0 = fn_w(&W_arr_arr[1][0]);
+  let w_arr2_arrI = fn_w(&W_arr_arr[2][I]);
+  let w_arrI_arr2 = fn_w(&W_arr_arr[I][2]);
+  let w_arrI_arrJ = fn_w(&W_arr_arr[I][J]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<i32>,
+}
+
+@group(0) @binding(0) var<uniform> U : vec4<i32>;
+
+@group(0) @binding(1) var<uniform> U_str : str;
+
+@group(0) @binding(2) var<uniform> U_arr : array<vec4<i32>, 8>;
+
+@group(0) @binding(3) var<uniform> U_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+@group(1) @binding(0) var<storage> S : vec4<i32>;
+
+@group(1) @binding(1) var<storage> S_str : str;
+
+@group(1) @binding(2) var<storage> S_arr : array<vec4<i32>, 8>;
+
+@group(1) @binding(3) var<storage> S_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+var<workgroup> W : vec4<i32>;
+
+var<workgroup> W_str : str;
+
+var<workgroup> W_arr : array<vec4<i32>, 8>;
+
+var<workgroup> W_arr_arr : array<array<vec4<i32>, 8>, 4>;
+
+fn fn_u_U() -> vec4<i32> {
+  return U;
+}
+
+fn fn_u_U_str_i() -> vec4<i32> {
+  return U_str.i;
+}
+
+type U_arr_X = array<u32, 1u>;
+
+fn fn_u_U_arr_X(p : U_arr_X) -> vec4<i32> {
+  return U_arr[p[0]];
+}
+
+type U_arr_arr_X_X = array<u32, 2u>;
+
+fn fn_u_U_arr_arr_X_X(p : U_arr_arr_X_X) -> vec4<i32> {
+  return U_arr_arr[p[0]][p[1]];
+}
+
+fn fn_s_S() -> vec4<i32> {
+  return S;
+}
+
+fn fn_s_S_str_i() -> vec4<i32> {
+  return S_str.i;
+}
+
+type S_arr_X = array<u32, 1u>;
+
+fn fn_s_S_arr_X(p : S_arr_X) -> vec4<i32> {
+  return S_arr[p[0]];
+}
+
+type S_arr_arr_X_X = array<u32, 2u>;
+
+fn fn_s_S_arr_arr_X_X(p : S_arr_arr_X_X) -> vec4<i32> {
+  return S_arr_arr[p[0]][p[1]];
+}
+
+fn fn_w_W() -> vec4<i32> {
+  return W;
+}
+
+fn fn_w_W_str_i() -> vec4<i32> {
+  return W_str.i;
+}
+
+type W_arr_X = array<u32, 1u>;
+
+fn fn_w_W_arr_X(p : W_arr_X) -> vec4<i32> {
+  return W_arr[p[0]];
+}
+
+type W_arr_arr_X_X = array<u32, 2u>;
+
+fn fn_w_W_arr_arr_X_X(p : W_arr_arr_X_X) -> vec4<i32> {
+  return W_arr_arr[p[0]][p[1]];
+}
+
+fn b() {
+  let I = 3;
+  let J = 4;
+  let u = fn_u_U();
+  let u_str = fn_u_U_str_i();
+  let u_arr0 = fn_u_U_arr_X(U_arr_X(0));
+  let u_arr1 = fn_u_U_arr_X(U_arr_X(1));
+  let u_arrI = fn_u_U_arr_X(U_arr_X(u32(I)));
+  let u_arr1_arr0 = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(1, 0));
+  let u_arr2_arrI = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(2, u32(I)));
+  let u_arrI_arr2 = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(u32(I), 2));
+  let u_arrI_arrJ = fn_u_U_arr_arr_X_X(U_arr_arr_X_X(u32(I), u32(J)));
+  let s = fn_s_S();
+  let s_str = fn_s_S_str_i();
+  let s_arr0 = fn_s_S_arr_X(S_arr_X(0));
+  let s_arr1 = fn_s_S_arr_X(S_arr_X(1));
+  let s_arrI = fn_s_S_arr_X(S_arr_X(u32(I)));
+  let s_arr1_arr0 = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(1, 0));
+  let s_arr2_arrI = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(2, u32(I)));
+  let s_arrI_arr2 = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(u32(I), 2));
+  let s_arrI_arrJ = fn_s_S_arr_arr_X_X(S_arr_arr_X_X(u32(I), u32(J)));
+  let w = fn_w_W();
+  let w_str = fn_w_W_str_i();
+  let w_arr0 = fn_w_W_arr_X(W_arr_X(0));
+  let w_arr1 = fn_w_W_arr_X(W_arr_X(1));
+  let w_arrI = fn_w_W_arr_X(W_arr_X(u32(I)));
+  let w_arr1_arr0 = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(1, 0));
+  let w_arr2_arrI = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(2, u32(I)));
+  let w_arrI_arr2 = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(u32(I), 2));
+  let w_arrI_arrJ = fn_w_W_arr_arr_X_X(W_arr_arr_X_X(u32(I), u32(J)));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessComplexTest, Indexing) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 { return i; }
+
+fn b(p : ptr<storage, array<array<array<i32, 9>, 9>, 9>>) -> i32 {
+  return (*p) [ a( (*p)[0][1][2]    )]
+              [ a( (*p)[a(3)][4][5] )]
+              [ a( (*p)[6][a(7)][8] )];
+}
+
+fn c() {
+  let v = b(&S[42]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 {
+  return i;
+}
+
+type S_X = array<u32, 1u>;
+
+fn b_S_X(p : S_X) -> i32 {
+  return S[p[0]][a(S[p[0]][0][1][2])][a(S[p[0]][a(3)][4][5])][a(S[p[0]][6][a(7)][8])];
+}
+
+fn c() {
+  let v = b_S_X(S_X(42));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessComplexTest, IndexingInPtrCall) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+fn a(pre : i32, i : ptr<storage, i32>, post : i32) -> i32 {
+  return *i;
+}
+
+fn b(p : ptr<storage, array<array<array<i32, 9>, 9>, 9>>) -> i32 {
+  return a(10, &(*p)[ a( 20, &(*p)[0][1][2], 30 )]
+                    [ a( 40, &(*p)[3][4][5], 50 )]
+                    [ a( 60, &(*p)[6][7][8], 70 )], 80);
+}
+
+fn c() {
+  let v = b(&S[42]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<array<i32, 9>, 9>, 9>, 50>;
+
+type S_X_X_X_X = array<u32, 4u>;
+
+fn a_S_X_X_X_X(pre : i32, i : S_X_X_X_X, post : i32) -> i32 {
+  return S[i[0]][i[0]][i[1]][i[2]];
+}
+
+type S_X = array<u32, 1u>;
+
+fn b_S_X(p : S_X) -> i32 {
+  return a_S_X_X_X_X(10, S_X_X_X_X(p[0u], u32(a_S_X_X_X_X(20, S_X_X_X_X(p[0u], 0, 1, 2), 30)), u32(a_S_X_X_X_X(40, S_X_X_X_X(p[0u], 3, 4, 5), 50)), u32(a_S_X_X_X_X(60, S_X_X_X_X(p[0u], 6, 7, 8), 70))), 80);
+}
+
+fn c() {
+  let v = b_S_X(S_X(42));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(DirectVariableAccessComplexTest, IndexingDualPointers) {
+    auto* src = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<i32, 9>, 9>, 50>;
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 { return i; }
+
+fn b(s : ptr<storage, array<array<i32, 9>, 9>>,
+     u : ptr<uniform, array<array<vec4<i32>, 9>, 9>>) -> i32 {
+  return (*s) [ a( (*u)[0][1].x    )]
+              [ a( (*u)[a(3)][4].y )];
+}
+
+fn c() {
+  let v = b(&S[42], &U[24]);
+}
+)";
+
+    auto* expect = R"(
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : array<array<array<i32, 9>, 9>, 50>;
+
+@group(0) @binding(0) var<uniform> U : array<array<array<vec4<i32>, 9>, 9>, 50>;
+
+fn a(i : i32) -> i32 {
+  return i;
+}
+
+type S_X = array<u32, 1u>;
+
+type U_X = array<u32, 1u>;
+
+fn b_S_X_U_X(s : S_X, u : U_X) -> i32 {
+  return S[s[0]][a(U[u[0]][0][1].x)][a(U[u[0]][a(3)][4].y)];
+}
+
+fn c() {
+  let v = b_S_X_U_X(S_X(42), U_X(24));
+}
+)";
+
+    auto got = Run<DirectVariableAccess>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace complex_tests
+
+}  // namespace
+}  // namespace tint::transform
diff --git a/src/tint/transform/unshadow.h b/src/tint/transform/unshadow.h
index 8ebf105..9030006 100644
--- a/src/tint/transform/unshadow.h
+++ b/src/tint/transform/unshadow.h
@@ -19,8 +19,7 @@
 
 namespace tint::transform {
 
-/// Unshadow is a Transform that renames any variables that shadow another
-/// variable.
+/// Unshadow is a Transform that renames any variables that shadow another variable.
 class Unshadow final : public Castable<Unshadow, Transform> {
   public:
     /// Constructor
diff --git a/src/tint/utils/block_allocator.h b/src/tint/utils/block_allocator.h
index 91a8a53..e75a0c6 100644
--- a/src/tint/utils/block_allocator.h
+++ b/src/tint/utils/block_allocator.h
@@ -39,6 +39,7 @@
         static constexpr size_t kMax = 32;
         std::array<T*, kMax> ptrs;
         Pointers* next;
+        Pointers* prev;
     };
 
     /// Block is linked list of memory blocks.
@@ -55,7 +56,7 @@
     class TView;
 
     /// An iterator for the objects owned by the BlockAllocator.
-    template <bool IS_CONST>
+    template <bool IS_CONST, bool FORWARD>
     class TIterator {
         using PointerTy = std::conditional_t<IS_CONST, const T*, T*>;
 
@@ -72,15 +73,24 @@
         /// @returns true if this iterator is not equal to other
         bool operator!=(const TIterator& other) const { return !(*this == other); }
 
-        /// Advances the iterator
+        /// Progress the iterator forward one element
         /// @returns this iterator
         TIterator& operator++() {
-            if (ptrs != nullptr) {
-                ++idx;
-                if (idx == Pointers::kMax) {
-                    idx = 0;
-                    ptrs = ptrs->next;
-                }
+            if (FORWARD) {
+                ProgressForward();
+            } else {
+                ProgressBackwards();
+            }
+            return *this;
+        }
+
+        /// Progress the iterator backwards one element
+        /// @returns this iterator
+        TIterator& operator--() {
+            if (FORWARD) {
+                ProgressBackwards();
+            } else {
+                ProgressForward();
             }
             return *this;
         }
@@ -92,6 +102,27 @@
         friend TView<IS_CONST>;  // Keep internal iterator impl private.
         explicit TIterator(const Pointers* p, size_t i) : ptrs(p), idx(i) {}
 
+        /// Progresses the iterator forwards
+        void ProgressForward() {
+            if (ptrs != nullptr) {
+                ++idx;
+                if (idx == Pointers::kMax) {
+                    idx = 0;
+                    ptrs = ptrs->next;
+                }
+            }
+        }
+        /// Progresses the iterator backwards
+        void ProgressBackwards() {
+            if (ptrs != nullptr) {
+                if (idx == 0) {
+                    idx = Pointers::kMax - 1;
+                    ptrs = ptrs->prev;
+                }
+                --idx;
+            }
+        }
+
         const Pointers* ptrs;
         size_t idx;
     };
@@ -102,16 +133,25 @@
     class TView {
       public:
         /// @returns an iterator to the beginning of the view
-        TIterator<IS_CONST> begin() const {
-            return TIterator<IS_CONST>{allocator_->data.pointers.root, 0};
+        TIterator<IS_CONST, true> begin() const {
+            return TIterator<IS_CONST, true>{allocator_->data.pointers.root, 0};
         }
 
         /// @returns an iterator to the end of the view
-        TIterator<IS_CONST> end() const {
+        TIterator<IS_CONST, true> end() const {
             return allocator_->data.pointers.current_index >= Pointers::kMax
-                       ? TIterator<IS_CONST>(nullptr, 0)
-                       : TIterator<IS_CONST>(allocator_->data.pointers.current,
-                                             allocator_->data.pointers.current_index);
+                       ? TIterator<IS_CONST, true>{nullptr, 0}
+                       : TIterator<IS_CONST, true>{allocator_->data.pointers.current,
+                                                   allocator_->data.pointers.current_index};
+        }
+
+        /// @returns an iterator to the beginning of the view
+        TIterator<IS_CONST, false> rbegin() const { return TIterator<IS_CONST, false>{nullptr, 0}; }
+
+        /// @returns an iterator to the end of the view
+        TIterator<IS_CONST, false> rend() const {
+            return TIterator<IS_CONST, false>{allocator_->data.pointers.current,
+                                              allocator_->data.pointers.current_index};
         }
 
       private:
@@ -121,11 +161,17 @@
     };
 
   public:
-    /// An iterator type over the objects of the BlockAllocator
-    using Iterator = TIterator<false>;
+    /// A forward-iterator type over the objects of the BlockAllocator
+    using Iterator = TIterator</* const */ false, /* forward */ true>;
 
-    /// An immutable iterator type over the objects of the BlockAllocator
-    using ConstIterator = TIterator<true>;
+    /// An immutable forward-iterator type over the objects of the BlockAllocator
+    using ConstIterator = TIterator</* const */ true, /* forward */ true>;
+
+    /// A reverse-iterator type over the objects of the BlockAllocator
+    using ReverseIterator = TIterator</* const */ false, /* forward */ false>;
+
+    /// An immutable reverse-iterator type over the objects of the BlockAllocator
+    using ReverseConstIterator = TIterator</* const */ true, /* forward */ false>;
 
     /// View provides begin() and end() methods for looping over the objects owned by the
     /// BlockAllocator.
@@ -248,6 +294,7 @@
                 return;  // out of memory
             }
             pointers.current->next = nullptr;
+            pointers.current->prev = prev_pointers;
             pointers.current_index = 0;
 
             if (prev_pointers) {
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 86ba657..f304d82 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -54,6 +54,7 @@
 #include "src/tint/transform/combine_samplers.h"
 #include "src/tint/transform/decompose_memory_access.h"
 #include "src/tint/transform/demote_to_helper.h"
+#include "src/tint/transform/direct_variable_access.h"
 #include "src/tint/transform/disable_uniformity_analysis.h"
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/manager.h"
@@ -209,7 +210,8 @@
     manager.Add<transform::Renamer>();
     data.Add<transform::Renamer::Config>(transform::Renamer::Target::kGlslKeywords,
                                          /* preserve_unicode */ false);
-    manager.Add<transform::Unshadow>();
+    manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
+    manager.Add<transform::DirectVariableAccess>();
 
     if (!options.disable_workgroup_init) {
         // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 1e786da..9d09795 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -54,6 +54,7 @@
 #include "src/tint/transform/canonicalize_entry_point_io.h"
 #include "src/tint/transform/decompose_memory_access.h"
 #include "src/tint/transform/demote_to_helper.h"
+#include "src/tint/transform/direct_variable_access.h"
 #include "src/tint/transform/disable_uniformity_analysis.h"
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/localize_struct_array_assignment.h"
@@ -194,7 +195,9 @@
     }
     manager.Add<transform::MultiplanarExternalTexture>();
 
-    manager.Add<transform::Unshadow>();
+    manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
+
+    manager.Add<transform::DirectVariableAccess>();
 
     // LocalizeStructArrayAssignment must come after:
     // * SimplifyPointers, because it assumes assignment to arrays in structs are
@@ -291,6 +294,7 @@
                                   utils::Vector{
                                       ast::Extension::kChromiumDisableUniformityAnalysis,
                                       ast::Extension::kChromiumExperimentalDp4A,
+                                      ast::Extension::kChromiumExperimentalFullPtrParameters,
                                       ast::Extension::kChromiumExperimentalPushConstant,
                                       ast::Extension::kF16,
                                   })) {
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 10efd59..9d06439 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -266,6 +266,7 @@
     if (!CheckSupportedExtensions("MSL", program_->AST(), diagnostics_,
                                   utils::Vector{
                                       ast::Extension::kChromiumDisableUniformityAnalysis,
+                                      ast::Extension::kChromiumExperimentalFullPtrParameters,
                                       ast::Extension::kChromiumExperimentalPushConstant,
                                       ast::Extension::kF16,
                                   })) {
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index c5e4a38..f2db430 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -260,6 +260,7 @@
                                   utils::Vector{
                                       ast::Extension::kChromiumDisableUniformityAnalysis,
                                       ast::Extension::kChromiumExperimentalDp4A,
+                                      ast::Extension::kChromiumExperimentalFullPtrParameters,
                                       ast::Extension::kChromiumExperimentalPushConstant,
                                       ast::Extension::kF16,
                                   })) {
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index 18c7511..b6bb0ff 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -22,6 +22,7 @@
 #include "src/tint/transform/builtin_polyfill.h"
 #include "src/tint/transform/canonicalize_entry_point_io.h"
 #include "src/tint/transform/demote_to_helper.h"
+#include "src/tint/transform/direct_variable_access.h"
 #include "src/tint/transform/disable_uniformity_analysis.h"
 #include "src/tint/transform/expand_compound_assignment.h"
 #include "src/tint/transform/for_loop_to_loop.h"
@@ -77,12 +78,21 @@
     }
     manager.Add<transform::MultiplanarExternalTexture>();
 
-    manager.Add<transform::Unshadow>();
+    manager.Add<transform::Unshadow>();  // Must come before DirectVariableAccess
     bool disable_workgroup_init_in_sanitizer =
         options.disable_workgroup_init || options.use_zero_initialize_workgroup_memory_extension;
     if (!disable_workgroup_init_in_sanitizer) {
         manager.Add<transform::ZeroInitWorkgroupMemory>();
     }
+
+    {
+        transform::DirectVariableAccess::Options opts;
+        opts.transform_private = true;
+        opts.transform_function = true;
+        data.Add<transform::DirectVariableAccess::Config>(opts);
+        manager.Add<transform::DirectVariableAccess>();
+    }
+
     manager.Add<transform::RemoveUnreachableStatements>();
     manager.Add<transform::PromoteSideEffectsToDecl>();
     manager.Add<transform::SimplifyPointers>();  // Required for arrayLength()
diff --git a/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl
new file mode 100644
index 0000000..4053c2a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn func(pointer : ptr<function, array<i32, 4>>) -> array<i32, 4> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  let r = func(&F.arr);
+}
diff --git a/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..f5feba0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int arr[4];
+};
+
+typedef int func_ret[4];
+func_ret func(inout int pointer[4]) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  const int r[4] = func(F.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..f5feba0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int arr[4];
+};
+
+typedef int func_ret[4];
+func_ret func(inout int pointer[4]) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  const int r[4] = func(F.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..63655fa
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+int[4] func(inout int pointer[4]) {
+  return pointer;
+}
+
+void tint_symbol() {
+  str F = str(int[4](0, 0, 0, 0));
+  int r[4] = func(F.arr);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..58faac8
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,30 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  tint_array<int, 4> arr;
+};
+
+tint_array<int, 4> func(thread tint_array<int, 4>* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  str F = {};
+  tint_array<int, 4> const r = func(&(F.arr));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..7ed320b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,42 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 24
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %func_F_arr "func_F_arr"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+               OpMemberDecorate %str 0 Offset 0
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+%_ptr_Function_str = OpTypePointer Function %str
+          %1 = OpTypeFunction %_arr_int_uint_4 %_ptr_Function_str
+     %uint_0 = OpConstant %uint 0
+%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
+       %void = OpTypeVoid
+         %16 = OpTypeFunction %void
+         %21 = OpConstantNull %str
+ %func_F_arr = OpFunction %_arr_int_uint_4 None %1
+    %pointer = OpFunctionParameter %_ptr_Function_str
+         %10 = OpLabel
+         %14 = OpAccessChain %_ptr_Function__arr_int_uint_4 %pointer %uint_0
+         %15 = OpLoad %_arr_int_uint_4 %14
+               OpReturnValue %15
+               OpFunctionEnd
+       %main = OpFunction %void None %16
+         %19 = OpLabel
+          %F = OpVariable %_ptr_Function_str Function %21
+         %22 = OpFunctionCall %_arr_int_uint_4 %func_F_arr %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..8bd5276
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn func(pointer : ptr<function, array<i32, 4>>) -> array<i32, 4> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  let r = func(&(F.arr));
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32.wgsl b/test/tint/ptr_ref/load/param/function/i32.wgsl
new file mode 100644
index 0000000..177b0ce
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : i32;
+  let r = func(&F);
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..fa36f9e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+int func(inout int pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  int F = 0;
+  const int r = func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..fa36f9e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+int func(inout int pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  int F = 0;
+  const int r = func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..c02a1da
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+void tint_symbol() {
+  int F = 0;
+  int r = func(F);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.msl
new file mode 100644
index 0000000..672f345
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+int func(thread int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  int F = 0;
+  int const r = func(&(F));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d25f603
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.spvasm
@@ -0,0 +1,31 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+          %1 = OpTypeFunction %int %_ptr_Function_int
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+         %14 = OpConstantNull %int
+       %func = OpFunction %int None %1
+    %pointer = OpFunctionParameter %_ptr_Function_int
+          %6 = OpLabel
+          %8 = OpLoad %int %pointer
+               OpReturnValue %8
+               OpFunctionEnd
+       %main = OpFunction %void None %9
+         %12 = OpLabel
+          %F = OpVariable %_ptr_Function_int Function %14
+         %15 = OpFunctionCall %int %func %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..805ee79
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32.wgsl.expected.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : i32;
+  let r = func(&(F));
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl
new file mode 100644
index 0000000..c3b7d55
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<function, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  let r = func(&F.i);
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..f726064
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  int i;
+};
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  const int r = func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..f726064
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  int i;
+};
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  const int r = func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..7a0bfb7
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+void tint_symbol() {
+  str F = str(0);
+  int r = func(F.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..ccbfc42
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  int i;
+};
+
+int func(thread int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  str F = {};
+  int const r = func(&(F.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..91e9a98
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,39 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 22
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+               OpMemberDecorate %str 0 Offset 0
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+%_ptr_Function_str = OpTypePointer Function %str
+          %1 = OpTypeFunction %int %_ptr_Function_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Function_int = OpTypePointer Function %int
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+         %19 = OpConstantNull %str
+   %func_F_i = OpFunction %int None %1
+    %pointer = OpFunctionParameter %_ptr_Function_str
+          %7 = OpLabel
+         %12 = OpAccessChain %_ptr_Function_int %pointer %uint_0
+         %13 = OpLoad %int %12
+               OpReturnValue %13
+               OpFunctionEnd
+       %main = OpFunction %void None %14
+         %17 = OpLabel
+          %F = OpVariable %_ptr_Function_str Function %19
+         %20 = OpFunctionCall %int %func_F_i %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..e5ecd70
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<function, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  let r = func(&(F.i));
+}
diff --git a/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl
new file mode 100644
index 0000000..103a3dd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<function, str>) -> str {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : array<str, 4>;
+  let r = func(&F[2]);
+}
diff --git a/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..c166cc2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  int i;
+};
+
+str func(inout str pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F[4] = (str[4])0;
+  const str r = func(F[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..c166cc2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  int i;
+};
+
+str func(inout str pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F[4] = (str[4])0;
+  const str r = func(F[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..8fc6fcd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+str func(inout str pointer) {
+  return pointer;
+}
+
+void tint_symbol() {
+  str F[4] = str[4](str(0), str(0), str(0), str(0));
+  str r = func(F[2]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..8205c73
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,30 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  int i;
+};
+
+str func(thread str* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  tint_array<str, 4> F = {};
+  str const r = func(&(F[2]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..2d239bd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,50 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpName %F "F"
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+%_ptr_Function__arr_str_uint_4 = OpTypePointer Function %_arr_str_uint_4
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %1 = OpTypeFunction %str %_ptr_Function__arr_str_uint_4 %_arr_uint_uint_1
+         %15 = OpConstantNull %int
+%_ptr_Function_str = OpTypePointer Function %str
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void
+         %25 = OpConstantNull %_arr_str_uint_4
+     %uint_2 = OpConstant %uint 2
+         %29 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+   %func_F_X = OpFunction %str None %1
+%pointer_base = OpFunctionParameter %_ptr_Function__arr_str_uint_4
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %13 = OpLabel
+         %16 = OpCompositeExtract %uint %pointer_indices 0
+         %18 = OpAccessChain %_ptr_Function_str %pointer_base %16
+         %19 = OpLoad %str %18
+               OpReturnValue %19
+               OpFunctionEnd
+       %main = OpFunction %void None %20
+         %23 = OpLabel
+          %F = OpVariable %_ptr_Function__arr_str_uint_4 Function %25
+         %26 = OpFunctionCall %str %func_F_X %F %29
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..2d4edb3
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<function, str>) -> str {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : array<str, 4>;
+  let r = func(&(F[2]));
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..afe82bc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec2<f32>>) -> vec2<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x2<f32>;
+  let r = func(&F[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8bc0349
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+float2 func(inout float2 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x2 F = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+  const float2 r = func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8bc0349
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+float2 func(inout float2 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x2 F = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+  const float2 r = func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..b50f64b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+vec2 func(inout vec2 pointer) {
+  return pointer;
+}
+
+void tint_symbol() {
+  mat2 F = mat2(0.0f, 0.0f, 0.0f, 0.0f);
+  vec2 r = func(F[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..e582ee2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float2 func(thread float2* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  float2x2 F = float2x2(0.0f);
+  float2 const r = func(&(F[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..f738c43
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,45 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpName %F "F"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+%_ptr_Function_mat2v2float = OpTypePointer Function %mat2v2float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %1 = OpTypeFunction %v2float %_ptr_Function_mat2v2float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %15 = OpConstantNull %int
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void
+         %25 = OpConstantNull %mat2v2float
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %v2float None %1
+%pointer_base = OpFunctionParameter %_ptr_Function_mat2v2float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %12 = OpLabel
+         %16 = OpCompositeExtract %uint %pointer_indices 0
+         %18 = OpAccessChain %_ptr_Function_v2float %pointer_base %16
+         %19 = OpLoad %v2float %18
+               OpReturnValue %19
+               OpFunctionEnd
+       %main = OpFunction %void None %20
+         %23 = OpLabel
+          %F = OpVariable %_ptr_Function_mat2v2float Function %25
+         %26 = OpFunctionCall %v2float %func_F_X %F %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..2c61e24
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec2<f32>>) -> vec2<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x2<f32>;
+  let r = func(&(F[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl
new file mode 100644
index 0000000..01aa487
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : vec4<f32>;
+  let r = func(&F);
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..67932df
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float4 F = float4(0.0f, 0.0f, 0.0f, 0.0f);
+  const float4 r = func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..67932df
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float4 F = float4(0.0f, 0.0f, 0.0f, 0.0f);
+  const float4 r = func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..a3cff7b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+vec4 func(inout vec4 pointer) {
+  return pointer;
+}
+
+void tint_symbol() {
+  vec4 F = vec4(0.0f, 0.0f, 0.0f, 0.0f);
+  vec4 r = func(F);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..a5f8113
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(thread float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  float4 F = 0.0f;
+  float4 const r = func(&(F));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..51c798e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+          %1 = OpTypeFunction %v4float %_ptr_Function_v4float
+       %void = OpTypeVoid
+         %10 = OpTypeFunction %void
+         %15 = OpConstantNull %v4float
+       %func = OpFunction %v4float None %1
+    %pointer = OpFunctionParameter %_ptr_Function_v4float
+          %7 = OpLabel
+          %9 = OpLoad %v4float %pointer
+               OpReturnValue %9
+               OpFunctionEnd
+       %main = OpFunction %void None %10
+         %13 = OpLabel
+          %F = OpVariable %_ptr_Function_v4float Function %15
+         %16 = OpFunctionCall %v4float %func %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..d7e7a55
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : vec4<f32>;
+  let r = func(&(F));
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..8b95569
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x4<f32>;
+  let r = func(&F[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..aec4b25
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x4 F = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  const float4 r = func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..aec4b25
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x4 F = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  const float4 r = func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..1819b4f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+vec4 func(inout vec4 pointer) {
+  return pointer;
+}
+
+void tint_symbol() {
+  mat2x4 F = mat2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  vec4 r = func(F[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..284613c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(thread float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  float2x4 F = float2x4(0.0f);
+  float4 const r = func(&(F[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..e2b9a41
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,45 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpName %F "F"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+%_ptr_Function_mat2v4float = OpTypePointer Function %mat2v4float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %1 = OpTypeFunction %v4float %_ptr_Function_mat2v4float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %15 = OpConstantNull %int
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void
+         %25 = OpConstantNull %mat2v4float
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %v4float None %1
+%pointer_base = OpFunctionParameter %_ptr_Function_mat2v4float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %12 = OpLabel
+         %16 = OpCompositeExtract %uint %pointer_indices 0
+         %18 = OpAccessChain %_ptr_Function_v4float %pointer_base %16
+         %19 = OpLoad %v4float %18
+               OpReturnValue %19
+               OpFunctionEnd
+       %main = OpFunction %void None %20
+         %23 = OpLabel
+          %F = OpVariable %_ptr_Function_mat2v4float Function %25
+         %26 = OpFunctionCall %v4float %func_F_X %F %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..e5a91d7
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x4<f32>;
+  let r = func(&(F[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..f1167ac
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+fn func(pointer : ptr<function, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  let r = func(&F.i);
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8cbef3d
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  float4 i;
+};
+
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  const float4 r = func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8cbef3d
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  float4 i;
+};
+
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  const float4 r = func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..5848312
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+vec4 func(inout vec4 pointer) {
+  return pointer;
+}
+
+void tint_symbol() {
+  str F = str(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  vec4 r = func(F.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..738a398
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  float4 i;
+};
+
+float4 func(thread float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  str F = {};
+  float4 const r = func(&(F.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..840f20e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,40 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 23
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+               OpMemberDecorate %str 0 Offset 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+%_ptr_Function_str = OpTypePointer Function %str
+          %1 = OpTypeFunction %v4float %_ptr_Function_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+         %20 = OpConstantNull %str
+   %func_F_i = OpFunction %v4float None %1
+    %pointer = OpFunctionParameter %_ptr_Function_str
+          %8 = OpLabel
+         %13 = OpAccessChain %_ptr_Function_v4float %pointer %uint_0
+         %14 = OpLoad %v4float %13
+               OpReturnValue %14
+               OpFunctionEnd
+       %main = OpFunction %void None %15
+         %18 = OpLabel
+          %F = OpVariable %_ptr_Function_str Function %20
+         %21 = OpFunctionCall %v4float %func_F_i %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..f35fc26
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/function/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+fn func(pointer : ptr<function, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  let r = func(&(F.i));
+}
diff --git a/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl
new file mode 100644
index 0000000..867e62b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn func(pointer : ptr<private, array<i32, 4>>) -> array<i32, 4> {
+  return *pointer;
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P.arr);
+}
diff --git a/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..6fc7d6c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,16 @@
+struct str {
+  int arr[4];
+};
+
+typedef int func_ret[4];
+func_ret func(inout int pointer[4]) {
+  return pointer;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r[4] = func(P.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..6fc7d6c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,16 @@
+struct str {
+  int arr[4];
+};
+
+typedef int func_ret[4];
+func_ret func(inout int pointer[4]) {
+  return pointer;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r[4] = func(P.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..b4dc836
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+int[4] func(inout int pointer[4]) {
+  return pointer;
+}
+
+str P = str(int[4](0, 0, 0, 0));
+void tint_symbol() {
+  int r[4] = func(P.arr);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..86e77bc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,30 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  tint_array<int, 4> arr;
+};
+
+tint_array<int, 4> func(thread tint_array<int, 4>* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread str tint_symbol_1 = {};
+  tint_array<int, 4> const r = func(&(tint_symbol_1.arr));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..ac1c5af
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,42 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 24
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %P "P"
+               OpName %func_F_arr "func_F_arr"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+%_ptr_Private_str = OpTypePointer Private %str
+          %8 = OpConstantNull %str
+          %P = OpVariable %_ptr_Private_str Private %8
+          %9 = OpTypeFunction %_arr_int_uint_4 %_ptr_Private_str
+     %uint_0 = OpConstant %uint 0
+%_ptr_Private__arr_int_uint_4 = OpTypePointer Private %_arr_int_uint_4
+       %void = OpTypeVoid
+         %18 = OpTypeFunction %void
+ %func_F_arr = OpFunction %_arr_int_uint_4 None %9
+    %pointer = OpFunctionParameter %_ptr_Private_str
+         %12 = OpLabel
+         %16 = OpAccessChain %_ptr_Private__arr_int_uint_4 %pointer %uint_0
+         %17 = OpLoad %_arr_int_uint_4 %16
+               OpReturnValue %17
+               OpFunctionEnd
+       %main = OpFunction %void None %18
+         %21 = OpLabel
+         %22 = OpFunctionCall %_arr_int_uint_4 %func_F_arr %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..1db57bb
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn func(pointer : ptr<private, array<i32, 4>>) -> array<i32, 4> {
+  return *(pointer);
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P.arr));
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32.wgsl b/test/tint/ptr_ref/load/param/private/i32.wgsl
new file mode 100644
index 0000000..ddbeb18
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, i32>) -> i32 {
+  return *pointer;
+}
+
+var<private> P : i32;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P);
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..1813860
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+int func(inout int pointer) {
+  return pointer;
+}
+
+static int P = 0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..1813860
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+int func(inout int pointer) {
+  return pointer;
+}
+
+static int P = 0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..5472203
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+int P = 0;
+void tint_symbol() {
+  int r = func(P);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.msl
new file mode 100644
index 0000000..7fa4197
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+int func(thread int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread int tint_symbol_1 = 0;
+  int const r = func(&(tint_symbol_1));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..66b4e2f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.spvasm
@@ -0,0 +1,31 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+        %int = OpTypeInt 32 1
+%_ptr_Private_int = OpTypePointer Private %int
+          %4 = OpConstantNull %int
+          %P = OpVariable %_ptr_Private_int Private %4
+          %5 = OpTypeFunction %int %_ptr_Private_int
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void
+       %func = OpFunction %int None %5
+    %pointer = OpFunctionParameter %_ptr_Private_int
+          %8 = OpLabel
+         %10 = OpLoad %int %pointer
+               OpReturnValue %10
+               OpFunctionEnd
+       %main = OpFunction %void None %11
+         %14 = OpLabel
+         %15 = OpFunctionCall %int %func %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..f049dce
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32.wgsl.expected.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, i32>) -> i32 {
+  return *(pointer);
+}
+
+var<private> P : i32;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P));
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl
new file mode 100644
index 0000000..3db15dc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<private, i32>) -> i32 {
+  return *pointer;
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P.i);
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..a2027f1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..a2027f1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..6b9c34b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+int func(inout int pointer) {
+  return pointer;
+}
+
+str P = str(0);
+void tint_symbol() {
+  int r = func(P.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..8ab2385
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  int i;
+};
+
+int func(thread int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread str tint_symbol_1 = {};
+  int const r = func(&(tint_symbol_1.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..1678d7e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,39 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 22
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %P "P"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+%_ptr_Private_str = OpTypePointer Private %str
+          %5 = OpConstantNull %str
+          %P = OpVariable %_ptr_Private_str Private %5
+          %6 = OpTypeFunction %int %_ptr_Private_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Private_int = OpTypePointer Private %int
+       %void = OpTypeVoid
+         %16 = OpTypeFunction %void
+   %func_F_i = OpFunction %int None %6
+    %pointer = OpFunctionParameter %_ptr_Private_str
+          %9 = OpLabel
+         %14 = OpAccessChain %_ptr_Private_int %pointer %uint_0
+         %15 = OpLoad %int %14
+               OpReturnValue %15
+               OpFunctionEnd
+       %main = OpFunction %void None %16
+         %19 = OpLabel
+         %20 = OpFunctionCall %int %func_F_i %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..bda048a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<private, i32>) -> i32 {
+  return *(pointer);
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P.i));
+}
diff --git a/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl
new file mode 100644
index 0000000..b7b8f90
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<private, str>) -> str {
+  return *pointer;
+}
+
+var<private> P : array<str, 4>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P[2]);
+}
diff --git a/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..077d5c3
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+str func(inout str pointer) {
+  return pointer;
+}
+
+static str P[4] = (str[4])0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const str r = func(P[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..077d5c3
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+str func(inout str pointer) {
+  return pointer;
+}
+
+static str P[4] = (str[4])0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const str r = func(P[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..d7225dd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+str func(inout str pointer) {
+  return pointer;
+}
+
+str P[4] = str[4](str(0), str(0), str(0), str(0));
+void tint_symbol() {
+  str r = func(P[2]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..e3794a9
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,30 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  int i;
+};
+
+str func(thread str* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread tint_array<str, 4> tint_symbol_1 = {};
+  str const r = func(&(tint_symbol_1[2]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..e96ddde
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,50 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %P "P"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+%_ptr_Private__arr_str_uint_4 = OpTypePointer Private %_arr_str_uint_4
+          %8 = OpConstantNull %_arr_str_uint_4
+          %P = OpVariable %_ptr_Private__arr_str_uint_4 Private %8
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %str %_ptr_Private__arr_str_uint_4 %_arr_uint_uint_1
+         %17 = OpConstantNull %int
+%_ptr_Private_str = OpTypePointer Private %str
+       %void = OpTypeVoid
+         %22 = OpTypeFunction %void
+     %uint_2 = OpConstant %uint 2
+         %29 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+   %func_F_X = OpFunction %str None %9
+%pointer_base = OpFunctionParameter %_ptr_Private__arr_str_uint_4
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer_indices 0
+         %20 = OpAccessChain %_ptr_Private_str %pointer_base %18
+         %21 = OpLoad %str %20
+               OpReturnValue %21
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %25 = OpLabel
+         %26 = OpFunctionCall %str %func_F_X %P %29
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..839270e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<private, str>) -> str {
+  return *(pointer);
+}
+
+var<private> P : array<str, 4>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P[2]));
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..7dc8a0b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec2<f32>>) -> vec2<f32> {
+  return *pointer;
+}
+
+var<private> P : mat2x2<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..eb92433
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+float2 func(inout float2 pointer) {
+  return pointer;
+}
+
+static float2x2 P = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float2 r = func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..eb92433
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+float2 func(inout float2 pointer) {
+  return pointer;
+}
+
+static float2x2 P = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float2 r = func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..f1b7b79
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+vec2 func(inout vec2 pointer) {
+  return pointer;
+}
+
+mat2 P = mat2(0.0f, 0.0f, 0.0f, 0.0f);
+void tint_symbol() {
+  vec2 r = func(P[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..27b7a26
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float2 func(thread float2* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread float2x2 tint_symbol_1 = float2x2(0.0f);
+  float2 const r = func(&(tint_symbol_1[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..8908962
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,45 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+%_ptr_Private_mat2v2float = OpTypePointer Private %mat2v2float
+          %6 = OpConstantNull %mat2v2float
+          %P = OpVariable %_ptr_Private_mat2v2float Private %6
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %v2float %_ptr_Private_mat2v2float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %17 = OpConstantNull %int
+%_ptr_Private_v2float = OpTypePointer Private %v2float
+       %void = OpTypeVoid
+         %22 = OpTypeFunction %void
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %v2float None %7
+%pointer_base = OpFunctionParameter %_ptr_Private_mat2v2float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer_indices 0
+         %20 = OpAccessChain %_ptr_Private_v2float %pointer_base %18
+         %21 = OpLoad %v2float %20
+               OpReturnValue %21
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %25 = OpLabel
+         %26 = OpFunctionCall %v2float %func_F_X %P %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..dbc825a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec2<f32>>) -> vec2<f32> {
+  return *(pointer);
+}
+
+var<private> P : mat2x2<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl
new file mode 100644
index 0000000..2acc575
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+var<private> P : vec4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P);
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..e7456b1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+static float4 P = float4(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..e7456b1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+static float4 P = float4(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..2ac5fd1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+vec4 func(inout vec4 pointer) {
+  return pointer;
+}
+
+vec4 P = vec4(0.0f, 0.0f, 0.0f, 0.0f);
+void tint_symbol() {
+  vec4 r = func(P);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..5b17645
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(thread float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread float4 tint_symbol_1 = 0.0f;
+  float4 const r = func(&(tint_symbol_1));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..dfe4930
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+          %5 = OpConstantNull %v4float
+          %P = OpVariable %_ptr_Private_v4float Private %5
+          %6 = OpTypeFunction %v4float %_ptr_Private_v4float
+       %void = OpTypeVoid
+         %12 = OpTypeFunction %void
+       %func = OpFunction %v4float None %6
+    %pointer = OpFunctionParameter %_ptr_Private_v4float
+          %9 = OpLabel
+         %11 = OpLoad %v4float %pointer
+               OpReturnValue %11
+               OpFunctionEnd
+       %main = OpFunction %void None %12
+         %15 = OpLabel
+         %16 = OpFunctionCall %v4float %func %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..824d5fd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+var<private> P : vec4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P));
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..07dfea9
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+var<private> P : mat2x4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..e0bb62e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+static float2x4 P = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..e0bb62e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+static float2x4 P = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..ba1b4b9
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+vec4 func(inout vec4 pointer) {
+  return pointer;
+}
+
+mat2x4 P = mat2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+void tint_symbol() {
+  vec4 r = func(P[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..f2112e8
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(thread float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread float2x4 tint_symbol_1 = float2x4(0.0f);
+  float4 const r = func(&(tint_symbol_1[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..2694e57
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,45 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+%_ptr_Private_mat2v4float = OpTypePointer Private %mat2v4float
+          %6 = OpConstantNull %mat2v4float
+          %P = OpVariable %_ptr_Private_mat2v4float Private %6
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %v4float %_ptr_Private_mat2v4float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %17 = OpConstantNull %int
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+       %void = OpTypeVoid
+         %22 = OpTypeFunction %void
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %v4float None %7
+%pointer_base = OpFunctionParameter %_ptr_Private_mat2v4float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer_indices 0
+         %20 = OpAccessChain %_ptr_Private_v4float %pointer_base %18
+         %21 = OpLoad %v4float %20
+               OpReturnValue %21
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %25 = OpLabel
+         %26 = OpFunctionCall %v4float %func_F_X %P %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..c0cf801
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+var<private> P : mat2x4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..90f325e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+fn func(pointer : ptr<private, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&P.i);
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..d489348
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  float4 i;
+};
+
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..d489348
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  float4 i;
+};
+
+float4 func(inout float4 pointer) {
+  return pointer;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..58fdade
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+vec4 func(inout vec4 pointer) {
+  return pointer;
+}
+
+str P = str(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+void tint_symbol() {
+  vec4 r = func(P.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..256f2a4
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  float4 i;
+};
+
+float4 func(thread float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol() {
+  thread str tint_symbol_1 = {};
+  float4 const r = func(&(tint_symbol_1.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..ff7b019
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,40 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 23
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %P "P"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+%_ptr_Private_str = OpTypePointer Private %str
+          %6 = OpConstantNull %str
+          %P = OpVariable %_ptr_Private_str Private %6
+          %7 = OpTypeFunction %v4float %_ptr_Private_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+       %void = OpTypeVoid
+         %17 = OpTypeFunction %void
+   %func_F_i = OpFunction %v4float None %7
+    %pointer = OpFunctionParameter %_ptr_Private_str
+         %10 = OpLabel
+         %15 = OpAccessChain %_ptr_Private_v4float %pointer %uint_0
+         %16 = OpLoad %v4float %15
+               OpReturnValue %16
+               OpFunctionEnd
+       %main = OpFunction %void None %17
+         %20 = OpLabel
+         %21 = OpFunctionCall %v4float %func_F_i %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..2c488a9
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/private/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+fn func(pointer : ptr<private, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(P.i));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl
new file mode 100644
index 0000000..eed9668
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn func(pointer : ptr<storage, array<i32, 4>>) -> array<i32, 4> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.arr);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..a3f14af
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,23 @@
+ByteAddressBuffer S : register(t0, space0);
+
+typedef int tint_symbol_ret[4];
+tint_symbol_ret tint_symbol(ByteAddressBuffer buffer, uint offset) {
+  int arr_1[4] = (int[4])0;
+  {
+    for(uint i = 0u; (i < 4u); i = (i + 1u)) {
+      arr_1[i] = asint(buffer.Load((offset + (i * 4u))));
+    }
+  }
+  return arr_1;
+}
+
+typedef int func_S_arr_ret[4];
+func_S_arr_ret func_S_arr() {
+  return tint_symbol(S, 0u);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r[4] = func_S_arr();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..a3f14af
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,23 @@
+ByteAddressBuffer S : register(t0, space0);
+
+typedef int tint_symbol_ret[4];
+tint_symbol_ret tint_symbol(ByteAddressBuffer buffer, uint offset) {
+  int arr_1[4] = (int[4])0;
+  {
+    for(uint i = 0u; (i < 4u); i = (i + 1u)) {
+      arr_1[i] = asint(buffer.Load((offset + (i * 4u))));
+    }
+  }
+  return arr_1;
+}
+
+typedef int func_S_arr_ret[4];
+func_S_arr_ret func_S_arr() {
+  return tint_symbol(S, 0u);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r[4] = func_S_arr();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..478f625
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner;
+} S;
+
+int[4] func_S_arr() {
+  return S.inner.arr;
+}
+
+void tint_symbol() {
+  int r[4] = func_S_arr();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..7f08b7b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,29 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  /* 0x0000 */ tint_array<int, 4> arr;
+};
+
+tint_array<int, 4> func(const device tint_array<int, 4>* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device str* tint_symbol_1 [[buffer(0)]]) {
+  tint_array<int, 4> const r = func(&((*(tint_symbol_1)).arr));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..ca7d725
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,47 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 21
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %S "S"
+               OpName %func_S_arr "func_S_arr"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+    %S_block = OpTypeStruct %str
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+          %9 = OpTypeFunction %_arr_int_uint_4
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer__arr_int_uint_4 = OpTypePointer StorageBuffer %_arr_int_uint_4
+       %void = OpTypeVoid
+         %16 = OpTypeFunction %void
+ %func_S_arr = OpFunction %_arr_int_uint_4 None %9
+         %11 = OpLabel
+         %14 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %S %uint_0 %uint_0
+         %15 = OpLoad %_arr_int_uint_4 %14
+               OpReturnValue %15
+               OpFunctionEnd
+       %main = OpFunction %void None %16
+         %19 = OpLabel
+         %20 = OpFunctionCall %_arr_int_uint_4 %func_S_arr
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..033d9b1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn func(pointer : ptr<storage, array<i32, 4>>) -> array<i32, 4> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.arr));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32.wgsl b/test/tint/ptr_ref/load/param/storage/i32.wgsl
new file mode 100644
index 0000000..ac2a585
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : i32;
+
+fn func(pointer : ptr<storage, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8322bbc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+int func_S() {
+  return asint(S.Load(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8322bbc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+int func_S() {
+  return asint(S.Load(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..a9a51fc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.glsl
@@ -0,0 +1,19 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  int inner;
+} S;
+
+int func_S() {
+  return S.inner;
+}
+
+void tint_symbol() {
+  int r = func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.msl
new file mode 100644
index 0000000..c0bac8c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+int func(const device int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device int* tint_symbol_1 [[buffer(0)]]) {
+  int const r = func(tint_symbol_1);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..571ba3e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.spvasm
@@ -0,0 +1,40 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+    %S_block = OpTypeStruct %int
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+          %5 = OpTypeFunction %int
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+       %void = OpTypeVoid
+         %13 = OpTypeFunction %void
+     %func_S = OpFunction %int None %5
+          %7 = OpLabel
+         %11 = OpAccessChain %_ptr_StorageBuffer_int %S %uint_0
+         %12 = OpLoad %int %11
+               OpReturnValue %12
+               OpFunctionEnd
+       %main = OpFunction %void None %13
+         %16 = OpLabel
+         %17 = OpFunctionCall %int %func_S
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..b9a422c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : i32;
+
+fn func(pointer : ptr<storage, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl
new file mode 100644
index 0000000..05631d2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn func(pointer : ptr<storage, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.i);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..c314820
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+int func_S_i() {
+  return asint(S.Load(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..c314820
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+int func_S_i() {
+  return asint(S.Load(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..5cfc748
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner;
+} S;
+
+int func_S_i() {
+  return S.inner.i;
+}
+
+void tint_symbol() {
+  int r = func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..e26f656
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,16 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  /* 0x0000 */ int i;
+};
+
+int func(const device int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device str* tint_symbol_1 [[buffer(0)]]) {
+  int const r = func(&((*(tint_symbol_1)).i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..5feaca5
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,44 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+    %S_block = OpTypeStruct %str
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+          %6 = OpTypeFunction %int
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+   %func_S_i = OpFunction %int None %6
+          %8 = OpLabel
+         %12 = OpAccessChain %_ptr_StorageBuffer_int %S %uint_0 %uint_0
+         %13 = OpLoad %int %12
+               OpReturnValue %13
+               OpFunctionEnd
+       %main = OpFunction %void None %14
+         %17 = OpLabel
+         %18 = OpFunctionCall %int %func_S_i
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..21763e0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn func(pointer : ptr<storage, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl
new file mode 100644
index 0000000..5616c9a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+@group(0) @binding(0) var<storage> S : array<str, 4>;
+
+fn func(pointer : ptr<storage, str>) -> str {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[2]);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..05f09cb
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,21 @@
+struct str {
+  int i;
+};
+
+ByteAddressBuffer S : register(t0, space0);
+
+str tint_symbol(ByteAddressBuffer buffer, uint offset) {
+  const str tint_symbol_2 = {asint(buffer.Load((offset + 0u)))};
+  return tint_symbol_2;
+}
+
+str func_S_X(uint pointer[1]) {
+  return tint_symbol(S, (4u * pointer[0]));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_3[1] = {2u};
+  const str r = func_S_X(tint_symbol_3);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..05f09cb
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,21 @@
+struct str {
+  int i;
+};
+
+ByteAddressBuffer S : register(t0, space0);
+
+str tint_symbol(ByteAddressBuffer buffer, uint offset) {
+  const str tint_symbol_2 = {asint(buffer.Load((offset + 0u)))};
+  return tint_symbol_2;
+}
+
+str func_S_X(uint pointer[1]) {
+  return tint_symbol(S, (4u * pointer[0]));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_3[1] = {2u};
+  const str r = func_S_X(tint_symbol_3);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..911d7c4
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,24 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner[4];
+} S;
+
+str func_S_X(uint pointer[1]) {
+  return S.inner[pointer[0]];
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](2u);
+  str r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..437bc2a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,29 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  /* 0x0000 */ int i;
+};
+
+str func(const device str* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device tint_array<str, 4>* tint_symbol_1 [[buffer(0)]]) {
+  str const r = func(&((*(tint_symbol_1))[2]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..6472fd8
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,56 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 28
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+    %S_block = OpTypeStruct %_arr_str_uint_4
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %str %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+         %16 = OpConstantNull %int
+%_ptr_StorageBuffer_str = OpTypePointer StorageBuffer %str
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+     %uint_2 = OpConstant %uint 2
+         %27 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+   %func_S_X = OpFunction %str None %9
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer 0
+         %19 = OpAccessChain %_ptr_StorageBuffer_str %S %uint_0 %17
+         %20 = OpLoad %str %19
+               OpReturnValue %20
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %24 = OpLabel
+         %25 = OpFunctionCall %str %func_S_X %27
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..e16f195
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+@group(0) @binding(0) var<storage> S : array<str, 4>;
+
+fn func(pointer : ptr<storage, str>) -> str {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[2]));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..180f5ce
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : mat2x2<f32>;
+
+fn func(pointer : ptr<storage, vec2<f32>>) -> vec2<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..809f70e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,12 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float2 func_S_X(uint pointer[1]) {
+  return asfloat(S.Load2((8u * pointer[0])));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float2 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..809f70e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,12 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float2 func_S_X(uint pointer[1]) {
+  return asfloat(S.Load2((8u * pointer[0])));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float2 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..35baf18
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  mat2 inner;
+} S;
+
+vec2 func_S_X(uint pointer[1]) {
+  return S.inner[pointer[0]];
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](1u);
+  vec2 r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..5155ddd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float2 func(const device float2* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device float2x2* tint_symbol_1 [[buffer(0)]]) {
+  float2 const r = func(&((*(tint_symbol_1))[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..82bc42c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,53 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 27
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %S_block 0 ColMajor
+               OpMemberDecorate %S_block 0 MatrixStride 8
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+    %S_block = OpTypeStruct %mat2v2float
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %v2float %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+        %int = OpTypeInt 32 1
+         %16 = OpConstantNull %int
+%_ptr_StorageBuffer_v2float = OpTypePointer StorageBuffer %v2float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+         %26 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_S_X = OpFunction %v2float None %7
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %13 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer 0
+         %19 = OpAccessChain %_ptr_StorageBuffer_v2float %S %uint_0 %17
+         %20 = OpLoad %v2float %19
+               OpReturnValue %20
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %24 = OpLabel
+         %25 = OpFunctionCall %v2float %func_S_X %26
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..889c31b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : mat2x2<f32>;
+
+fn func(pointer : ptr<storage, vec2<f32>>) -> vec2<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl
new file mode 100644
index 0000000..2a08b41
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : vec4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..eca7796
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float4 func_S() {
+  return asfloat(S.Load4(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..eca7796
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float4 func_S() {
+  return asfloat(S.Load4(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..9db47b7
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,19 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  vec4 inner;
+} S;
+
+vec4 func_S() {
+  return S.inner;
+}
+
+void tint_symbol() {
+  vec4 r = func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..5ae6156
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(const device float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device float4* tint_symbol_1 [[buffer(0)]]) {
+  float4 const r = func(tint_symbol_1);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..1a72aae
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,41 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %S_block = OpTypeStruct %v4float
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+          %6 = OpTypeFunction %v4float
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+     %func_S = OpFunction %v4float None %6
+          %8 = OpLabel
+         %12 = OpAccessChain %_ptr_StorageBuffer_v4float %S %uint_0
+         %13 = OpLoad %v4float %12
+               OpReturnValue %13
+               OpFunctionEnd
+       %main = OpFunction %void None %14
+         %17 = OpLabel
+         %18 = OpFunctionCall %v4float %func_S
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..a1bcaca
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : vec4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..35ef536
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : mat2x4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..10793c7
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,12 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float4 func_S_X(uint pointer[1]) {
+  return asfloat(S.Load4((16u * pointer[0])));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float4 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..10793c7
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,12 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float4 func_S_X(uint pointer[1]) {
+  return asfloat(S.Load4((16u * pointer[0])));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float4 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..7e4c0b6
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  mat2x4 inner;
+} S;
+
+vec4 func_S_X(uint pointer[1]) {
+  return S.inner[pointer[0]];
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](1u);
+  vec4 r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..1948f6e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(const device float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device float2x4* tint_symbol_1 [[buffer(0)]]) {
+  float4 const r = func(&((*(tint_symbol_1))[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..8d13b84
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,53 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 27
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %S_block 0 ColMajor
+               OpMemberDecorate %S_block 0 MatrixStride 16
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+    %S_block = OpTypeStruct %mat2v4float
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %v4float %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+        %int = OpTypeInt 32 1
+         %16 = OpConstantNull %int
+%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+         %26 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_S_X = OpFunction %v4float None %7
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %13 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer 0
+         %19 = OpAccessChain %_ptr_StorageBuffer_v4float %S %uint_0 %17
+         %20 = OpLoad %v4float %19
+               OpReturnValue %20
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %24 = OpLabel
+         %25 = OpFunctionCall %v4float %func_S_X %26
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..a11814a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage> S : mat2x4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..5fe26b3
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn func(pointer : ptr<storage, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.i);
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..1fd53b9
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float4 func_S_i() {
+  return asfloat(S.Load4(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..1fd53b9
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+ByteAddressBuffer S : register(t0, space0);
+
+float4 func_S_i() {
+  return asfloat(S.Load4(0u));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..d98d91d
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner;
+} S;
+
+vec4 func_S_i() {
+  return S.inner.i;
+}
+
+void tint_symbol() {
+  vec4 r = func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..1a2a590
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,16 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  /* 0x0000 */ float4 i;
+};
+
+float4 func(const device float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const device str* tint_symbol_1 [[buffer(0)]]) {
+  float4 const r = func(&((*(tint_symbol_1)).i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..7abeef3
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,45 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 20
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+    %S_block = OpTypeStruct %str
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+          %7 = OpTypeFunction %v4float
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+   %func_S_i = OpFunction %v4float None %7
+          %9 = OpLabel
+         %13 = OpAccessChain %_ptr_StorageBuffer_v4float %S %uint_0 %uint_0
+         %14 = OpLoad %v4float %13
+               OpReturnValue %14
+               OpFunctionEnd
+       %main = OpFunction %void None %15
+         %18 = OpLabel
+         %19 = OpFunctionCall %v4float %func_S_i
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..bb2f4cc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/storage/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+@group(0) @binding(0) var<storage> S : str;
+
+fn func(pointer : ptr<storage, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl
new file mode 100644
index 0000000..cccfea0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<vec4<i32>, 4>,
+};
+
+@group(0) @binding(0) var<uniform> S : str;
+
+fn func(pointer : ptr<uniform, array<vec4<i32>, 4>>) -> array<vec4<i32>, 4> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.arr);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..df6d009
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,26 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[4];
+};
+
+typedef int4 tint_symbol_ret[4];
+tint_symbol_ret tint_symbol(uint4 buffer[4], uint offset) {
+  int4 arr_1[4] = (int4[4])0;
+  {
+    for(uint i = 0u; (i < 4u); i = (i + 1u)) {
+      const uint scalar_offset = ((offset + (i * 16u))) / 4;
+      arr_1[i] = asint(buffer[scalar_offset / 4]);
+    }
+  }
+  return arr_1;
+}
+
+typedef int4 func_S_arr_ret[4];
+func_S_arr_ret func_S_arr() {
+  return tint_symbol(S, 0u);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int4 r[4] = func_S_arr();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..df6d009
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,26 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[4];
+};
+
+typedef int4 tint_symbol_ret[4];
+tint_symbol_ret tint_symbol(uint4 buffer[4], uint offset) {
+  int4 arr_1[4] = (int4[4])0;
+  {
+    for(uint i = 0u; (i < 4u); i = (i + 1u)) {
+      const uint scalar_offset = ((offset + (i * 16u))) / 4;
+      arr_1[i] = asint(buffer[scalar_offset / 4]);
+    }
+  }
+  return arr_1;
+}
+
+typedef int4 func_S_arr_ret[4];
+func_S_arr_ret func_S_arr() {
+  return tint_symbol(S, 0u);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int4 r[4] = func_S_arr();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..3de54f2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+
+struct str {
+  ivec4 arr[4];
+};
+
+layout(binding = 0, std140) uniform S_block_ubo {
+  str inner;
+} S;
+
+ivec4[4] func_S_arr() {
+  return S.inner.arr;
+}
+
+void tint_symbol() {
+  ivec4 r[4] = func_S_arr();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..ff667b2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,29 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  /* 0x0000 */ tint_array<int4, 4> arr;
+};
+
+tint_array<int4, 4> func(const constant tint_array<int4, 4>* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant str* tint_symbol_1 [[buffer(0)]]) {
+  tint_array<int4, 4> const r = func(&((*(tint_symbol_1)).arr));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..ba3211f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,48 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 22
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %S "S"
+               OpName %func_S_arr "func_S_arr"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_v4int_uint_4 ArrayStride 16
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+      %v4int = OpTypeVector %int 4
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_v4int_uint_4 = OpTypeArray %v4int %uint_4
+        %str = OpTypeStruct %_arr_v4int_uint_4
+    %S_block = OpTypeStruct %str
+%_ptr_Uniform_S_block = OpTypePointer Uniform %S_block
+          %S = OpVariable %_ptr_Uniform_S_block Uniform
+         %10 = OpTypeFunction %_arr_v4int_uint_4
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform__arr_v4int_uint_4 = OpTypePointer Uniform %_arr_v4int_uint_4
+       %void = OpTypeVoid
+         %17 = OpTypeFunction %void
+ %func_S_arr = OpFunction %_arr_v4int_uint_4 None %10
+         %12 = OpLabel
+         %15 = OpAccessChain %_ptr_Uniform__arr_v4int_uint_4 %S %uint_0 %uint_0
+         %16 = OpLoad %_arr_v4int_uint_4 %15
+               OpReturnValue %16
+               OpFunctionEnd
+       %main = OpFunction %void None %17
+         %20 = OpLabel
+         %21 = OpFunctionCall %_arr_v4int_uint_4 %func_S_arr
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..c2b203e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<vec4<i32>, 4>,
+}
+
+@group(0) @binding(0) var<uniform> S : str;
+
+fn func(pointer : ptr<uniform, array<vec4<i32>, 4>>) -> array<vec4<i32>, 4> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.arr));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32.wgsl b/test/tint/ptr_ref/load/param/uniform/i32.wgsl
new file mode 100644
index 0000000..8e37001
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : i32;
+
+fn func(pointer : ptr<uniform, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..1c68252
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+int func_S() {
+  return asint(S[0].x);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..1c68252
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+int func_S() {
+  return asint(S[0].x);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..e30a7db
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.glsl
@@ -0,0 +1,19 @@
+#version 310 es
+
+layout(binding = 0, std140) uniform S_block_ubo {
+  int inner;
+} S;
+
+int func_S() {
+  return S.inner;
+}
+
+void tint_symbol() {
+  int r = func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.msl
new file mode 100644
index 0000000..8bb0365
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+int func(const constant int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant int* tint_symbol_1 [[buffer(0)]]) {
+  int const r = func(tint_symbol_1);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..7efbd09
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.spvasm
@@ -0,0 +1,40 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+    %S_block = OpTypeStruct %int
+%_ptr_Uniform_S_block = OpTypePointer Uniform %S_block
+          %S = OpVariable %_ptr_Uniform_S_block Uniform
+          %5 = OpTypeFunction %int
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+       %void = OpTypeVoid
+         %13 = OpTypeFunction %void
+     %func_S = OpFunction %int None %5
+          %7 = OpLabel
+         %11 = OpAccessChain %_ptr_Uniform_int %S %uint_0
+         %12 = OpLoad %int %11
+               OpReturnValue %12
+               OpFunctionEnd
+       %main = OpFunction %void None %13
+         %16 = OpLabel
+         %17 = OpFunctionCall %int %func_S
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..b8a873f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : i32;
+
+fn func(pointer : ptr<uniform, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl
new file mode 100644
index 0000000..6db13e0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+@group(0) @binding(0) var<uniform> S : str;
+
+fn func(pointer : ptr<uniform, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.i);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..f5f2ed2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+int func_S_i() {
+  return asint(S[0].x);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..f5f2ed2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+int func_S_i() {
+  return asint(S[0].x);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const int r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..284adc1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,26 @@
+#version 310 es
+
+struct str {
+  int i;
+  uint pad;
+  uint pad_1;
+  uint pad_2;
+};
+
+layout(binding = 0, std140) uniform S_block_ubo {
+  str inner;
+} S;
+
+int func_S_i() {
+  return S.inner.i;
+}
+
+void tint_symbol() {
+  int r = func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..f4a643f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,16 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  /* 0x0000 */ int i;
+};
+
+int func(const constant int* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant str* tint_symbol_1 [[buffer(0)]]) {
+  int const r = func(&((*(tint_symbol_1)).i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..2f1702d
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,44 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+    %S_block = OpTypeStruct %str
+%_ptr_Uniform_S_block = OpTypePointer Uniform %S_block
+          %S = OpVariable %_ptr_Uniform_S_block Uniform
+          %6 = OpTypeFunction %int
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_int = OpTypePointer Uniform %int
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+   %func_S_i = OpFunction %int None %6
+          %8 = OpLabel
+         %12 = OpAccessChain %_ptr_Uniform_int %S %uint_0 %uint_0
+         %13 = OpLoad %int %12
+               OpReturnValue %13
+               OpFunctionEnd
+       %main = OpFunction %void None %14
+         %17 = OpLabel
+         %18 = OpFunctionCall %int %func_S_i
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..c44d00b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+@group(0) @binding(0) var<uniform> S : str;
+
+fn func(pointer : ptr<uniform, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl
new file mode 100644
index 0000000..f0c98a4
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<i32>,
+};
+
+@group(0) @binding(0) var<uniform> S : array<str, 4>;
+
+fn func(pointer : ptr<uniform, str>) -> str {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[2]);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..cea8a9a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,24 @@
+struct str {
+  int4 i;
+};
+
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[4];
+};
+
+str tint_symbol(uint4 buffer[4], uint offset) {
+  const uint scalar_offset = ((offset + 0u)) / 4;
+  const str tint_symbol_2 = {asint(buffer[scalar_offset / 4])};
+  return tint_symbol_2;
+}
+
+str func_S_X(uint pointer[1]) {
+  return tint_symbol(S, (16u * pointer[0]));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_3[1] = {2u};
+  const str r = func_S_X(tint_symbol_3);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..cea8a9a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,24 @@
+struct str {
+  int4 i;
+};
+
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[4];
+};
+
+str tint_symbol(uint4 buffer[4], uint offset) {
+  const uint scalar_offset = ((offset + 0u)) / 4;
+  const str tint_symbol_2 = {asint(buffer[scalar_offset / 4])};
+  return tint_symbol_2;
+}
+
+str func_S_X(uint pointer[1]) {
+  return tint_symbol(S, (16u * pointer[0]));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_3[1] = {2u};
+  const str r = func_S_X(tint_symbol_3);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..c1620ec
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,24 @@
+#version 310 es
+
+struct str {
+  ivec4 i;
+};
+
+layout(binding = 0, std140) uniform S_block_ubo {
+  str inner[4];
+} S;
+
+str func_S_X(uint pointer[1]) {
+  return S.inner[pointer[0]];
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](2u);
+  str r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..83554f0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,29 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  /* 0x0000 */ int4 i;
+};
+
+str func(const constant str* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant tint_array<str, 4>* tint_symbol_1 [[buffer(0)]]) {
+  str const r = func(&((*(tint_symbol_1))[2]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..273b5bd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,57 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 16
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+        %int = OpTypeInt 32 1
+      %v4int = OpTypeVector %int 4
+        %str = OpTypeStruct %v4int
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+    %S_block = OpTypeStruct %_arr_str_uint_4
+%_ptr_Uniform_S_block = OpTypePointer Uniform %S_block
+          %S = OpVariable %_ptr_Uniform_S_block Uniform
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+         %10 = OpTypeFunction %str %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+         %17 = OpConstantNull %int
+%_ptr_Uniform_str = OpTypePointer Uniform %str
+       %void = OpTypeVoid
+         %22 = OpTypeFunction %void
+     %uint_2 = OpConstant %uint 2
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+   %func_S_X = OpFunction %str None %10
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer 0
+         %20 = OpAccessChain %_ptr_Uniform_str %S %uint_0 %18
+         %21 = OpLoad %str %20
+               OpReturnValue %21
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %25 = OpLabel
+         %26 = OpFunctionCall %str %func_S_X %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..be885de
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<i32>,
+}
+
+@group(0) @binding(0) var<uniform> S : array<str, 4>;
+
+fn func(pointer : ptr<uniform, str>) -> str {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[2]));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..e84f1a1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : mat2x2<f32>;
+
+fn func(pointer : ptr<uniform, vec2<f32>>) -> vec2<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..7718f57
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,16 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+float2 func_S_X(uint pointer[1]) {
+  const uint scalar_offset = ((8u * pointer[0])) / 4;
+  uint4 ubo_load = S[scalar_offset / 4];
+  return asfloat(((scalar_offset & 2) ? ubo_load.zw : ubo_load.xy));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float2 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..7718f57
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,16 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+float2 func_S_X(uint pointer[1]) {
+  const uint scalar_offset = ((8u * pointer[0])) / 4;
+  uint4 ubo_load = S[scalar_offset / 4];
+  return asfloat(((scalar_offset & 2) ? ubo_load.zw : ubo_load.xy));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float2 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..744936c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,38 @@
+#version 310 es
+
+layout(binding = 0, std140) uniform S_block_std140_ubo {
+  vec2 inner_0;
+  vec2 inner_1;
+} S;
+
+vec2 load_S_inner_p0(uint p0) {
+  switch(p0) {
+    case 0u: {
+      return S.inner_0;
+      break;
+    }
+    case 1u: {
+      return S.inner_1;
+      break;
+    }
+    default: {
+      return vec2(0.0f);
+      break;
+    }
+  }
+}
+
+vec2 func_S_X(uint pointer[1]) {
+  return load_S_inner_p0(uint(pointer[0]));
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](1u);
+  vec2 r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..47d02ac
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float2 func(const constant float2* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant float2x2* tint_symbol_1 [[buffer(0)]]) {
+  float2 const r = func(&((*(tint_symbol_1))[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..4025051
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,73 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 39
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block_std140 "S_block_std140"
+               OpMemberName %S_block_std140 0 "inner_0"
+               OpMemberName %S_block_std140 1 "inner_1"
+               OpName %S "S"
+               OpName %load_S_inner_p0 "load_S_inner_p0"
+               OpName %p0 "p0"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block_std140 Block
+               OpMemberDecorate %S_block_std140 0 Offset 0
+               OpMemberDecorate %S_block_std140 1 Offset 8
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%S_block_std140 = OpTypeStruct %v2float %v2float
+%_ptr_Uniform_S_block_std140 = OpTypePointer Uniform %S_block_std140
+          %S = OpVariable %_ptr_Uniform_S_block_std140 Uniform
+       %uint = OpTypeInt 32 0
+          %6 = OpTypeFunction %v2float %uint
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_v2float = OpTypePointer Uniform %v2float
+     %uint_1 = OpConstant %uint 1
+         %22 = OpConstantNull %v2float
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+         %23 = OpTypeFunction %v2float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %31 = OpConstantNull %int
+       %void = OpTypeVoid
+         %33 = OpTypeFunction %void
+         %38 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+%load_S_inner_p0 = OpFunction %v2float None %6
+         %p0 = OpFunctionParameter %uint
+         %10 = OpLabel
+               OpSelectionMerge %11 None
+               OpSwitch %p0 %12 0 %13 1 %14
+         %13 = OpLabel
+         %17 = OpAccessChain %_ptr_Uniform_v2float %S %uint_0
+         %18 = OpLoad %v2float %17
+               OpReturnValue %18
+         %14 = OpLabel
+         %20 = OpAccessChain %_ptr_Uniform_v2float %S %uint_1
+         %21 = OpLoad %v2float %20
+               OpReturnValue %21
+         %12 = OpLabel
+               OpReturnValue %22
+         %11 = OpLabel
+               OpReturnValue %22
+               OpFunctionEnd
+   %func_S_X = OpFunction %v2float None %23
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %27 = OpLabel
+         %32 = OpCompositeExtract %uint %pointer 0
+         %28 = OpFunctionCall %v2float %load_S_inner_p0 %32
+               OpReturnValue %28
+               OpFunctionEnd
+       %main = OpFunction %void None %33
+         %36 = OpLabel
+         %37 = OpFunctionCall %v2float %func_S_X %38
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..1891f2a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : mat2x2<f32>;
+
+fn func(pointer : ptr<uniform, vec2<f32>>) -> vec2<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl
new file mode 100644
index 0000000..68e7949
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : vec4<f32>;
+
+fn func(pointer : ptr<uniform, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..cd31c23
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+float4 func_S() {
+  return asfloat(S[0]);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..cd31c23
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+float4 func_S() {
+  return asfloat(S[0]);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..69cd92e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,19 @@
+#version 310 es
+
+layout(binding = 0, std140) uniform S_block_ubo {
+  vec4 inner;
+} S;
+
+vec4 func_S() {
+  return S.inner;
+}
+
+void tint_symbol() {
+  vec4 r = func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..6c0dc2c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(const constant float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant float4* tint_symbol_1 [[buffer(0)]]) {
+  float4 const r = func(tint_symbol_1);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d99abfc
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,41 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %S_block = OpTypeStruct %v4float
+%_ptr_Uniform_S_block = OpTypePointer Uniform %S_block
+          %S = OpVariable %_ptr_Uniform_S_block Uniform
+          %6 = OpTypeFunction %v4float
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+       %void = OpTypeVoid
+         %14 = OpTypeFunction %void
+     %func_S = OpFunction %v4float None %6
+          %8 = OpLabel
+         %12 = OpAccessChain %_ptr_Uniform_v4float %S %uint_0
+         %13 = OpLoad %v4float %12
+               OpReturnValue %13
+               OpFunctionEnd
+       %main = OpFunction %void None %14
+         %17 = OpLabel
+         %18 = OpFunctionCall %v4float %func_S
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..c5e8e6a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : vec4<f32>;
+
+fn func(pointer : ptr<uniform, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..a77ca7c
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : mat2x4<f32>;
+
+fn func(pointer : ptr<uniform, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8aeba47
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[2];
+};
+
+float4 func_S_X(uint pointer[1]) {
+  const uint scalar_offset = ((16u * pointer[0])) / 4;
+  return asfloat(S[scalar_offset / 4]);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float4 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8aeba47
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[2];
+};
+
+float4 func_S_X(uint pointer[1]) {
+  const uint scalar_offset = ((16u * pointer[0])) / 4;
+  return asfloat(S[scalar_offset / 4]);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  const float4 r = func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..0852e33
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+layout(binding = 0, std140) uniform S_block_ubo {
+  mat2x4 inner;
+} S;
+
+vec4 func_S_X(uint pointer[1]) {
+  return S.inner[pointer[0]];
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](1u);
+  vec4 r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..2b30f33
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(const constant float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant float2x4* tint_symbol_1 [[buffer(0)]]) {
+  float4 const r = func(&((*(tint_symbol_1))[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..6cdd4c0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,53 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 27
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %S_block 0 ColMajor
+               OpMemberDecorate %S_block 0 MatrixStride 16
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+    %S_block = OpTypeStruct %mat2v4float
+%_ptr_Uniform_S_block = OpTypePointer Uniform %S_block
+          %S = OpVariable %_ptr_Uniform_S_block Uniform
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %v4float %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+        %int = OpTypeInt 32 1
+         %16 = OpConstantNull %int
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+         %26 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_S_X = OpFunction %v4float None %7
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %13 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer 0
+         %19 = OpAccessChain %_ptr_Uniform_v4float %S %uint_0 %17
+         %20 = OpLoad %v4float %19
+               OpReturnValue %20
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %24 = OpLabel
+         %25 = OpFunctionCall %v4float %func_S_X %26
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..7f010b8
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<uniform> S : mat2x4<f32>;
+
+fn func(pointer : ptr<uniform, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..7059df4
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+@group(0) @binding(0) var<uniform> S : str;
+
+fn func(pointer : ptr<uniform, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.i);
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8861303
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+float4 func_S_i() {
+  return asfloat(S[0]);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8861303
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,13 @@
+cbuffer cbuffer_S : register(b0, space0) {
+  uint4 S[1];
+};
+
+float4 func_S_i() {
+  return asfloat(S[0]);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const float4 r = func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..46120ee
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+layout(binding = 0, std140) uniform S_block_ubo {
+  str inner;
+} S;
+
+vec4 func_S_i() {
+  return S.inner.i;
+}
+
+void tint_symbol() {
+  vec4 r = func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..68870b6
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,16 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  /* 0x0000 */ float4 i;
+};
+
+float4 func(const constant float4* const pointer) {
+  return *(pointer);
+}
+
+kernel void tint_symbol(const constant str* tint_symbol_1 [[buffer(0)]]) {
+  float4 const r = func(&((*(tint_symbol_1)).i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..9586d0b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,45 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 20
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %S NonWritable
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+    %S_block = OpTypeStruct %str
+%_ptr_Uniform_S_block = OpTypePointer Uniform %S_block
+          %S = OpVariable %_ptr_Uniform_S_block Uniform
+          %7 = OpTypeFunction %v4float
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void
+   %func_S_i = OpFunction %v4float None %7
+          %9 = OpLabel
+         %13 = OpAccessChain %_ptr_Uniform_v4float %S %uint_0 %uint_0
+         %14 = OpLoad %v4float %13
+               OpReturnValue %14
+               OpFunctionEnd
+       %main = OpFunction %void None %15
+         %18 = OpLabel
+         %19 = OpFunctionCall %v4float %func_S_i
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..f08290a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/uniform/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+@group(0) @binding(0) var<uniform> S : str;
+
+fn func(pointer : ptr<uniform, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl
new file mode 100644
index 0000000..55a558a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, array<i32, 4>>) -> array<i32, 4> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.arr);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..14498cd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,31 @@
+struct str {
+  int arr[4];
+};
+
+groupshared str S;
+
+typedef int func_S_arr_ret[4];
+func_S_arr_ret func_S_arr() {
+  return S.arr;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i = idx;
+      S.arr[i] = 0;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const int r[4] = func_S_arr();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..14498cd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,31 @@
+struct str {
+  int arr[4];
+};
+
+groupshared str S;
+
+typedef int func_S_arr_ret[4];
+func_S_arr_ret func_S_arr() {
+  return S.arr;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i = idx;
+      S.arr[i] = 0;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const int r[4] = func_S_arr();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..ce8bf4d
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,27 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+shared str S;
+int[4] func_S_arr() {
+  return S.arr;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      uint i = idx;
+      S.arr[i] = 0;
+    }
+  }
+  barrier();
+  int r[4] = func_S_arr();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..e8f0b1f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,39 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  tint_array<int, 4> arr;
+};
+
+tint_array<int, 4> func(threadgroup tint_array<int, 4>* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup str* const tint_symbol_1) {
+  for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    uint const i = idx;
+    (*(tint_symbol_1)).arr[i] = 0;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  tint_array<int, 4> const r = func(&((*(tint_symbol_1)).arr));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup str tint_symbol_2;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_2));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..687e862
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,88 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 51
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %S "S"
+               OpName %func_S_arr "func_S_arr"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %idx "idx"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+          %S = OpVariable %_ptr_Workgroup_str Workgroup
+         %10 = OpTypeFunction %_arr_int_uint_4
+     %uint_0 = OpConstant %uint 0
+%_ptr_Workgroup__arr_int_uint_4 = OpTypePointer Workgroup %_arr_int_uint_4
+       %void = OpTypeVoid
+         %17 = OpTypeFunction %void %uint
+%_ptr_Function_uint = OpTypePointer Function %uint
+         %24 = OpConstantNull %uint
+       %bool = OpTypeBool
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+         %38 = OpConstantNull %int
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %46 = OpTypeFunction %void
+ %func_S_arr = OpFunction %_arr_int_uint_4 None %10
+         %12 = OpLabel
+         %15 = OpAccessChain %_ptr_Workgroup__arr_int_uint_4 %S %uint_0
+         %16 = OpLoad %_arr_int_uint_4 %15
+               OpReturnValue %16
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %17
+%local_invocation_index = OpFunctionParameter %uint
+         %21 = OpLabel
+        %idx = OpVariable %_ptr_Function_uint Function %24
+               OpStore %idx %local_invocation_index
+               OpBranch %25
+         %25 = OpLabel
+               OpLoopMerge %26 %27 None
+               OpBranch %28
+         %28 = OpLabel
+         %30 = OpLoad %uint %idx
+         %31 = OpULessThan %bool %30 %uint_4
+         %29 = OpLogicalNot %bool %31
+               OpSelectionMerge %33 None
+               OpBranchConditional %29 %34 %33
+         %34 = OpLabel
+               OpBranch %26
+         %33 = OpLabel
+         %35 = OpLoad %uint %idx
+         %37 = OpAccessChain %_ptr_Workgroup_int %S %uint_0 %35
+               OpStore %37 %38
+               OpBranch %27
+         %27 = OpLabel
+         %39 = OpLoad %uint %idx
+         %41 = OpIAdd %uint %39 %uint_1
+               OpStore %idx %41
+               OpBranch %25
+         %26 = OpLabel
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %45 = OpFunctionCall %_arr_int_uint_4 %func_S_arr
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %46
+         %48 = OpLabel
+         %50 = OpLoad %uint %local_invocation_index_1
+         %49 = OpFunctionCall %void %main_inner %50
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..1368900
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, array<i32, 4>>) -> array<i32, 4> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.arr));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32.wgsl b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl
new file mode 100644
index 0000000..f0b1be9
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : i32;
+
+fn func(pointer : ptr<workgroup, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..b3368a2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,23 @@
+groupshared int S;
+
+int func_S() {
+  return S;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = 0;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const int r = func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..b3368a2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,23 @@
+groupshared int S;
+
+int func_S() {
+  return S;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = 0;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const int r = func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..fdab385
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+shared int S;
+int func_S() {
+  return S;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = 0;
+  }
+  barrier();
+  int r = func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.msl
new file mode 100644
index 0000000..d438840
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.msl
@@ -0,0 +1,21 @@
+#include <metal_stdlib>
+
+using namespace metal;
+int func(threadgroup int* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup int* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = 0;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  int const r = func(tint_symbol_1);
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup int tint_symbol_2;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_2));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..4b294ee
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.spvasm
@@ -0,0 +1,48 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 26
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+          %S = OpVariable %_ptr_Workgroup_int Workgroup
+          %7 = OpTypeFunction %int
+       %void = OpTypeVoid
+         %11 = OpTypeFunction %void %uint
+         %16 = OpConstantNull %int
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %21 = OpTypeFunction %void
+     %func_S = OpFunction %int None %7
+          %9 = OpLabel
+         %10 = OpLoad %int %S
+               OpReturnValue %10
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %11
+%local_invocation_index = OpFunctionParameter %uint
+         %15 = OpLabel
+               OpStore %S %16
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %20 = OpFunctionCall %int %func_S
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %23 = OpLabel
+         %25 = OpLoad %uint %local_invocation_index_1
+         %24 = OpFunctionCall %void %main_inner %25
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..8c26859
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : i32;
+
+fn func(pointer : ptr<workgroup, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl
new file mode 100644
index 0000000..b777a56
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, i32>) -> i32 {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.i);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8cf40a2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  int i;
+};
+
+groupshared str S;
+
+int func_S_i() {
+  return S.i;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const int r = func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8cf40a2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  int i;
+};
+
+groupshared str S;
+
+int func_S_i() {
+  return S.i;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const int r = func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..f59ffb0
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,25 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+shared str S;
+int func_S_i() {
+  return S.i;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    str tint_symbol_1 = str(0);
+    S = tint_symbol_1;
+  }
+  barrier();
+  int r = func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..514268e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,26 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  int i;
+};
+
+int func(threadgroup int* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup str* const tint_symbol_2) {
+  {
+    str const tint_symbol_1 = str{};
+    *(tint_symbol_2) = tint_symbol_1;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  int const r = func(&((*(tint_symbol_2)).i));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup str tint_symbol_3;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_3));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..9297831
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,55 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+          %S = OpVariable %_ptr_Workgroup_str Workgroup
+          %8 = OpTypeFunction %int
+     %uint_0 = OpConstant %uint 0
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+       %void = OpTypeVoid
+         %15 = OpTypeFunction %void %uint
+         %20 = OpConstantNull %str
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %25 = OpTypeFunction %void
+   %func_S_i = OpFunction %int None %8
+         %10 = OpLabel
+         %13 = OpAccessChain %_ptr_Workgroup_int %S %uint_0
+         %14 = OpLoad %int %13
+               OpReturnValue %14
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %15
+%local_invocation_index = OpFunctionParameter %uint
+         %19 = OpLabel
+               OpStore %S %20
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %24 = OpFunctionCall %int %func_S_i
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %25
+         %27 = OpLabel
+         %29 = OpLoad %uint %local_invocation_index_1
+         %28 = OpFunctionCall %void %main_inner %29
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..d335ec2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, i32>) -> i32 {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl
new file mode 100644
index 0000000..638ca03
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+var<workgroup> S : array<str, 4>;
+
+fn func(pointer : ptr<workgroup, str>) -> str {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[2]);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..1b5ca94
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,32 @@
+struct str {
+  int i;
+};
+
+groupshared str S[4];
+
+str func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i_1 = idx;
+      const str tint_symbol_2 = (str)0;
+      S[i_1] = tint_symbol_2;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_3[1] = {2u};
+  const str r = func_S_X(tint_symbol_3);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..1b5ca94
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,32 @@
+struct str {
+  int i;
+};
+
+groupshared str S[4];
+
+str func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i_1 = idx;
+      const str tint_symbol_2 = (str)0;
+      S[i_1] = tint_symbol_2;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_3[1] = {2u};
+  const str r = func_S_X(tint_symbol_3);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..2466387
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,29 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+shared str S[4];
+str func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      uint i_1 = idx;
+      str tint_symbol_1 = str(0);
+      S[i_1] = tint_symbol_1;
+    }
+  }
+  barrier();
+  uint tint_symbol_2[1] = uint[1](2u);
+  str r = func_S_X(tint_symbol_2);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..15c963d
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,40 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  int i;
+};
+
+str func(threadgroup str* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup tint_array<str, 4>* const tint_symbol_2) {
+  for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    uint const i_1 = idx;
+    str const tint_symbol_1 = str{};
+    (*(tint_symbol_2))[i_1] = tint_symbol_1;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  str const r = func(&((*(tint_symbol_2))[2]));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup tint_array<str, 4> tint_symbol_3;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_3));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..b0a6175
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,93 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 54
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %idx "idx"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+%_ptr_Workgroup__arr_str_uint_4 = OpTypePointer Workgroup %_arr_str_uint_4
+          %S = OpVariable %_ptr_Workgroup__arr_str_uint_4 Workgroup
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+         %10 = OpTypeFunction %str %_arr_uint_uint_1
+         %16 = OpConstantNull %int
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void %uint
+%_ptr_Function_uint = OpTypePointer Function %uint
+         %28 = OpConstantNull %uint
+       %bool = OpTypeBool
+         %41 = OpConstantNull %str
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %48 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+         %49 = OpTypeFunction %void
+   %func_S_X = OpFunction %str None %10
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer 0
+         %19 = OpAccessChain %_ptr_Workgroup_str %S %17
+         %20 = OpLoad %str %19
+               OpReturnValue %20
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %21
+%local_invocation_index = OpFunctionParameter %uint
+         %25 = OpLabel
+        %idx = OpVariable %_ptr_Function_uint Function %28
+               OpStore %idx %local_invocation_index
+               OpBranch %29
+         %29 = OpLabel
+               OpLoopMerge %30 %31 None
+               OpBranch %32
+         %32 = OpLabel
+         %34 = OpLoad %uint %idx
+         %35 = OpULessThan %bool %34 %uint_4
+         %33 = OpLogicalNot %bool %35
+               OpSelectionMerge %37 None
+               OpBranchConditional %33 %38 %37
+         %38 = OpLabel
+               OpBranch %30
+         %37 = OpLabel
+         %39 = OpLoad %uint %idx
+         %40 = OpAccessChain %_ptr_Workgroup_str %S %39
+               OpStore %40 %41
+               OpBranch %31
+         %31 = OpLabel
+         %42 = OpLoad %uint %idx
+         %43 = OpIAdd %uint %42 %uint_1
+               OpStore %idx %43
+               OpBranch %29
+         %30 = OpLabel
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %47 = OpFunctionCall %str %func_S_X %48
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %49
+         %51 = OpLabel
+         %53 = OpLoad %uint %local_invocation_index_1
+         %52 = OpFunctionCall %void %main_inner %53
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..2a586fb
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+var<workgroup> S : array<str, 4>;
+
+fn func(pointer : ptr<workgroup, str>) -> str {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[2]));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..5d8e9ae
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x2<f32>;
+
+fn func(pointer : ptr<workgroup, vec2<f32>>) -> vec2<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..9ce5c2e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,24 @@
+groupshared float2x2 S;
+
+float2 func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x2((0.0f).xx, (0.0f).xx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  const float2 r = func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..9ce5c2e
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,24 @@
+groupshared float2x2 S;
+
+float2 func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x2((0.0f).xx, (0.0f).xx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  const float2 r = func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..6208c08
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+shared mat2 S;
+vec2 func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = mat2(vec2(0.0f), vec2(0.0f));
+  }
+  barrier();
+  uint tint_symbol_1[1] = uint[1](1u);
+  vec2 r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..77d8114
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,25 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_4 {
+  float2x2 S;
+};
+
+float2 func(threadgroup float2* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup float2x2* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = float2x2(float2(0.0f), float2(0.0f));
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float2 const r = func(&((*(tint_symbol_1))[1]));
+}
+
+kernel void tint_symbol(threadgroup tint_symbol_4* tint_symbol_3 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float2x2* const tint_symbol_2 = &((*(tint_symbol_3)).S);
+  tint_symbol_inner(local_invocation_index, tint_symbol_2);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..513a654
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,61 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 37
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+%_ptr_Workgroup_mat2v2float = OpTypePointer Workgroup %mat2v2float
+          %S = OpVariable %_ptr_Workgroup_mat2v2float Workgroup
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %v2float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %16 = OpConstantNull %int
+%_ptr_Workgroup_v2float = OpTypePointer Workgroup %v2float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void %uint
+         %26 = OpConstantNull %mat2v2float
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %31 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+         %32 = OpTypeFunction %void
+   %func_S_X = OpFunction %v2float None %9
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer 0
+         %19 = OpAccessChain %_ptr_Workgroup_v2float %S %17
+         %20 = OpLoad %v2float %19
+               OpReturnValue %20
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %21
+%local_invocation_index = OpFunctionParameter %uint
+         %25 = OpLabel
+               OpStore %S %26
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %30 = OpFunctionCall %v2float %func_S_X %31
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %32
+         %34 = OpLabel
+         %36 = OpLoad %uint %local_invocation_index_1
+         %35 = OpFunctionCall %void %main_inner %36
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..55afec1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x2<f32>;
+
+fn func(pointer : ptr<workgroup, vec2<f32>>) -> vec2<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl
new file mode 100644
index 0000000..0ccdce7
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : vec4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..0273faf
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,23 @@
+groupshared float4 S;
+
+float4 func_S() {
+  return S;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = (0.0f).xxxx;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const float4 r = func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..0273faf
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,23 @@
+groupshared float4 S;
+
+float4 func_S() {
+  return S;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = (0.0f).xxxx;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const float4 r = func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..93c3bdd
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+shared vec4 S;
+vec4 func_S() {
+  return S;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = vec4(0.0f);
+  }
+  barrier();
+  vec4 r = func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..3a2d553
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,21 @@
+#include <metal_stdlib>
+
+using namespace metal;
+float4 func(threadgroup float4* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup float4* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = float4(0.0f);
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float4 const r = func(tint_symbol_1);
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float4 tint_symbol_2;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_2));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..eec223f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,49 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 27
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
+          %S = OpVariable %_ptr_Workgroup_v4float Workgroup
+          %8 = OpTypeFunction %v4float
+       %void = OpTypeVoid
+         %12 = OpTypeFunction %void %uint
+         %17 = OpConstantNull %v4float
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %22 = OpTypeFunction %void
+     %func_S = OpFunction %v4float None %8
+         %10 = OpLabel
+         %11 = OpLoad %v4float %S
+               OpReturnValue %11
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %12
+%local_invocation_index = OpFunctionParameter %uint
+         %16 = OpLabel
+               OpStore %S %17
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %21 = OpFunctionCall %v4float %func_S
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %24 = OpLabel
+         %26 = OpLoad %uint %local_invocation_index_1
+         %25 = OpFunctionCall %void %main_inner %26
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..3c9ab35
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : vec4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..204d6c2
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..4a0f881
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,24 @@
+groupshared float2x4 S;
+
+float4 func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x4((0.0f).xxxx, (0.0f).xxxx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  const float4 r = func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..4a0f881
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,24 @@
+groupshared float2x4 S;
+
+float4 func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x4((0.0f).xxxx, (0.0f).xxxx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  const float4 r = func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..0e1cc3b
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+shared mat2x4 S;
+vec4 func_S_X(uint pointer[1]) {
+  return S[pointer[0]];
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = mat2x4(vec4(0.0f), vec4(0.0f));
+  }
+  barrier();
+  uint tint_symbol_1[1] = uint[1](1u);
+  vec4 r = func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..992678f
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,25 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_4 {
+  float2x4 S;
+};
+
+float4 func(threadgroup float4* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup float2x4* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = float2x4(float4(0.0f), float4(0.0f));
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float4 const r = func(&((*(tint_symbol_1))[1]));
+}
+
+kernel void tint_symbol(threadgroup tint_symbol_4* tint_symbol_3 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float2x4* const tint_symbol_2 = &((*(tint_symbol_3)).S);
+  tint_symbol_inner(local_invocation_index, tint_symbol_2);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..8725df1
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,61 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 37
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+%_ptr_Workgroup_mat2v4float = OpTypePointer Workgroup %mat2v4float
+          %S = OpVariable %_ptr_Workgroup_mat2v4float Workgroup
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %v4float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %16 = OpConstantNull %int
+%_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void %uint
+         %26 = OpConstantNull %mat2v4float
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %31 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+         %32 = OpTypeFunction %void
+   %func_S_X = OpFunction %v4float None %9
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer 0
+         %19 = OpAccessChain %_ptr_Workgroup_v4float %S %17
+         %20 = OpLoad %v4float %19
+               OpReturnValue %20
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %21
+%local_invocation_index = OpFunctionParameter %uint
+         %25 = OpLabel
+               OpStore %S %26
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %30 = OpFunctionCall %v4float %func_S_X %31
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %32
+         %34 = OpLabel
+         %36 = OpLoad %uint %local_invocation_index_1
+         %35 = OpFunctionCall %void %main_inner %36
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..5f0f9ae
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..5251b66
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) -> vec4<f32> {
+  return *pointer;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&S.i);
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..5c05d92
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  float4 i;
+};
+
+groupshared str S;
+
+float4 func_S_i() {
+  return S.i;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const float4 r = func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..5c05d92
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  float4 i;
+};
+
+groupshared str S;
+
+float4 func_S_i() {
+  return S.i;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const float4 r = func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..863db2a
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,25 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+shared str S;
+vec4 func_S_i() {
+  return S.i;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    str tint_symbol_1 = str(vec4(0.0f));
+    S = tint_symbol_1;
+  }
+  barrier();
+  vec4 r = func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..b8dd6d8
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,26 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  float4 i;
+};
+
+float4 func(threadgroup float4* const pointer) {
+  return *(pointer);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup str* const tint_symbol_2) {
+  {
+    str const tint_symbol_1 = str{};
+    *(tint_symbol_2) = tint_symbol_1;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  float4 const r = func(&((*(tint_symbol_2)).i));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup str tint_symbol_3;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_3));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..1323b99
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,56 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 31
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+          %S = OpVariable %_ptr_Workgroup_str Workgroup
+          %9 = OpTypeFunction %v4float
+     %uint_0 = OpConstant %uint 0
+%_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
+       %void = OpTypeVoid
+         %16 = OpTypeFunction %void %uint
+         %21 = OpConstantNull %str
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %26 = OpTypeFunction %void
+   %func_S_i = OpFunction %v4float None %9
+         %11 = OpLabel
+         %14 = OpAccessChain %_ptr_Workgroup_v4float %S %uint_0
+         %15 = OpLoad %v4float %14
+               OpReturnValue %15
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %16
+%local_invocation_index = OpFunctionParameter %uint
+         %20 = OpLabel
+               OpStore %S %21
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %25 = OpFunctionCall %v4float %func_S_i
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %26
+         %28 = OpLabel
+         %30 = OpLoad %uint %local_invocation_index_1
+         %29 = OpFunctionCall %void %main_inner %30
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..0d3e1d4
--- /dev/null
+++ b/test/tint/ptr_ref/load/param/workgroup/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) -> vec4<f32> {
+  return *(pointer);
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  let r = func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl
new file mode 100644
index 0000000..c39eca4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn func(pointer : ptr<function, array<i32, 4>>) {
+  *pointer = array<i32, 4>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  func(&F.arr);
+}
diff --git a/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..ba0930f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int arr[4];
+};
+
+void func(inout int pointer[4]) {
+  const int tint_symbol[4] = (int[4])0;
+  pointer = tint_symbol;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  func(F.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..ba0930f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int arr[4];
+};
+
+void func(inout int pointer[4]) {
+  const int tint_symbol[4] = (int[4])0;
+  pointer = tint_symbol;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  func(F.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..c171f12
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+void func(inout int pointer[4]) {
+  int tint_symbol_1[4] = int[4](0, 0, 0, 0);
+  pointer = tint_symbol_1;
+}
+
+void tint_symbol() {
+  str F = str(int[4](0, 0, 0, 0));
+  func(F.arr);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..fb8a998
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,31 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  tint_array<int, 4> arr;
+};
+
+void func(thread tint_array<int, 4>* const pointer) {
+  tint_array<int, 4> const tint_symbol_1 = tint_array<int, 4>{};
+  *(pointer) = tint_symbol_1;
+}
+
+kernel void tint_symbol() {
+  str F = {};
+  func(&(F.arr));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..a39fb3b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,43 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 24
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %func_F_arr "func_F_arr"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+       %void = OpTypeVoid
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+%_ptr_Function_str = OpTypePointer Function %str
+          %1 = OpTypeFunction %void %_ptr_Function_str
+     %uint_0 = OpConstant %uint 0
+%_ptr_Function__arr_int_uint_4 = OpTypePointer Function %_arr_int_uint_4
+         %16 = OpConstantNull %_arr_int_uint_4
+         %17 = OpTypeFunction %void
+         %21 = OpConstantNull %str
+ %func_F_arr = OpFunction %void None %1
+    %pointer = OpFunctionParameter %_ptr_Function_str
+         %11 = OpLabel
+         %15 = OpAccessChain %_ptr_Function__arr_int_uint_4 %pointer %uint_0
+               OpStore %15 %16
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %17
+         %19 = OpLabel
+          %F = OpVariable %_ptr_Function_str Function %21
+         %22 = OpFunctionCall %void %func_F_arr %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..f45fcae
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn func(pointer : ptr<function, array<i32, 4>>) {
+  *(pointer) = array<i32, 4>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  func(&(F.arr));
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32.wgsl b/test/tint/ptr_ref/store/param/function/i32.wgsl
new file mode 100644
index 0000000..f64f384
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, i32>) {
+  *pointer = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : i32;
+  func(&F);
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..1b5ec00
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  int F = 0;
+  func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..1b5ec00
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  int F = 0;
+  func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..88ce758
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+void tint_symbol() {
+  int F = 0;
+  func(F);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.msl
new file mode 100644
index 0000000..39497aa
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread int* const pointer) {
+  *(pointer) = 42;
+}
+
+kernel void tint_symbol() {
+  int F = 0;
+  func(&(F));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..ed7174b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+       %void = OpTypeVoid
+        %int = OpTypeInt 32 1
+%_ptr_Function_int = OpTypePointer Function %int
+          %1 = OpTypeFunction %void %_ptr_Function_int
+     %int_42 = OpConstant %int 42
+         %10 = OpTypeFunction %void
+         %14 = OpConstantNull %int
+       %func = OpFunction %void None %1
+    %pointer = OpFunctionParameter %_ptr_Function_int
+          %7 = OpLabel
+               OpStore %pointer %int_42
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %10
+         %12 = OpLabel
+          %F = OpVariable %_ptr_Function_int Function %14
+         %15 = OpFunctionCall %void %func %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..de8ec37
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32.wgsl.expected.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, i32>) {
+  *(pointer) = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : i32;
+  func(&(F));
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl
new file mode 100644
index 0000000..971651c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<function, i32>) {
+  *pointer = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  func(&F.i);
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..701095e
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  int i;
+};
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..701095e
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  int i;
+};
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..d06a029
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+void tint_symbol() {
+  str F = str(0);
+  func(F.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..e5bc71a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  int i;
+};
+
+void func(thread int* const pointer) {
+  *(pointer) = 42;
+}
+
+kernel void tint_symbol() {
+  str F = {};
+  func(&(F.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..3c5bb74
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,40 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 22
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+               OpMemberDecorate %str 0 Offset 0
+       %void = OpTypeVoid
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+%_ptr_Function_str = OpTypePointer Function %str
+          %1 = OpTypeFunction %void %_ptr_Function_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Function_int = OpTypePointer Function %int
+     %int_42 = OpConstant %int 42
+         %15 = OpTypeFunction %void
+         %19 = OpConstantNull %str
+   %func_F_i = OpFunction %void None %1
+    %pointer = OpFunctionParameter %_ptr_Function_str
+          %8 = OpLabel
+         %13 = OpAccessChain %_ptr_Function_int %pointer %uint_0
+               OpStore %13 %int_42
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %15
+         %17 = OpLabel
+          %F = OpVariable %_ptr_Function_str Function %19
+         %20 = OpFunctionCall %void %func_F_i %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..3db8678
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<function, i32>) {
+  *(pointer) = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  func(&(F.i));
+}
diff --git a/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl
new file mode 100644
index 0000000..631b70a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<function, str>) {
+  *pointer = str();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : array<str, 4>;
+  func(&F[2]);
+}
diff --git a/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..13c0184
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+void func(inout str pointer) {
+  const str tint_symbol = (str)0;
+  pointer = tint_symbol;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F[4] = (str[4])0;
+  func(F[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..13c0184
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+void func(inout str pointer) {
+  const str tint_symbol = (str)0;
+  pointer = tint_symbol;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F[4] = (str[4])0;
+  func(F[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..88406352
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+void func(inout str pointer) {
+  str tint_symbol_1 = str(0);
+  pointer = tint_symbol_1;
+}
+
+void tint_symbol() {
+  str F[4] = str[4](str(0), str(0), str(0), str(0));
+  func(F[2]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..1242b36
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,31 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  int i;
+};
+
+void func(thread str* const pointer) {
+  str const tint_symbol_1 = str{};
+  *(pointer) = tint_symbol_1;
+}
+
+kernel void tint_symbol() {
+  tint_array<str, 4> F = {};
+  func(&(F[2]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..11f5024
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,51 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpName %F "F"
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %void = OpTypeVoid
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+%_ptr_Function__arr_str_uint_4 = OpTypePointer Function %_arr_str_uint_4
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %1 = OpTypeFunction %void %_ptr_Function__arr_str_uint_4 %_arr_uint_uint_1
+         %16 = OpConstantNull %int
+%_ptr_Function_str = OpTypePointer Function %str
+         %20 = OpConstantNull %str
+         %21 = OpTypeFunction %void
+         %25 = OpConstantNull %_arr_str_uint_4
+     %uint_2 = OpConstant %uint 2
+         %29 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+   %func_F_X = OpFunction %void None %1
+%pointer_base = OpFunctionParameter %_ptr_Function__arr_str_uint_4
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer_indices 0
+         %19 = OpAccessChain %_ptr_Function_str %pointer_base %17
+               OpStore %19 %20
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %23 = OpLabel
+          %F = OpVariable %_ptr_Function__arr_str_uint_4 Function %25
+         %26 = OpFunctionCall %void %func_F_X %F %29
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..fe15aa5
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<function, str>) {
+  *(pointer) = str();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : array<str, 4>;
+  func(&(F[2]));
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..3108042
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec2<f32>>) {
+  *pointer = vec2<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x2<f32>;
+  func(&F[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..e19db34
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout float2 pointer) {
+  pointer = (0.0f).xx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x2 F = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+  func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..e19db34
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout float2 pointer) {
+  pointer = (0.0f).xx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x2 F = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+  func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..947ecd6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout vec2 pointer) {
+  pointer = vec2(0.0f);
+}
+
+void tint_symbol() {
+  mat2 F = mat2(0.0f, 0.0f, 0.0f, 0.0f);
+  func(F[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..f8f7aed
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread float2* const pointer) {
+  *(pointer) = float2(0.0f);
+}
+
+kernel void tint_symbol() {
+  float2x2 F = float2x2(0.0f);
+  func(&(F[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..a8de55d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,46 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpName %F "F"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+%_ptr_Function_mat2v2float = OpTypePointer Function %mat2v2float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %1 = OpTypeFunction %void %_ptr_Function_mat2v2float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %16 = OpConstantNull %int
+%_ptr_Function_v2float = OpTypePointer Function %v2float
+         %20 = OpConstantNull %v2float
+         %21 = OpTypeFunction %void
+         %25 = OpConstantNull %mat2v2float
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %void None %1
+%pointer_base = OpFunctionParameter %_ptr_Function_mat2v2float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %13 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer_indices 0
+         %19 = OpAccessChain %_ptr_Function_v2float %pointer_base %17
+               OpStore %19 %20
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %23 = OpLabel
+          %F = OpVariable %_ptr_Function_mat2v2float Function %25
+         %26 = OpFunctionCall %void %func_F_X %F %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..a8dca7e
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec2<f32>>) {
+  *(pointer) = vec2<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x2<f32>;
+  func(&(F[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl
new file mode 100644
index 0000000..3055a86
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : vec4<f32>;
+  func(&F);
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..74c4735
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float4 F = float4(0.0f, 0.0f, 0.0f, 0.0f);
+  func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..74c4735
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float4 F = float4(0.0f, 0.0f, 0.0f, 0.0f);
+  func(F);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..6c2db02
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout vec4 pointer) {
+  pointer = vec4(0.0f);
+}
+
+void tint_symbol() {
+  vec4 F = vec4(0.0f, 0.0f, 0.0f, 0.0f);
+  func(F);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..e90fddf
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol() {
+  float4 F = 0.0f;
+  func(&(F));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..c6e3a08
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+          %1 = OpTypeFunction %void %_ptr_Function_v4float
+         %10 = OpConstantNull %v4float
+         %11 = OpTypeFunction %void
+       %func = OpFunction %void None %1
+    %pointer = OpFunctionParameter %_ptr_Function_v4float
+          %8 = OpLabel
+               OpStore %pointer %10
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %11
+         %13 = OpLabel
+          %F = OpVariable %_ptr_Function_v4float Function %10
+         %15 = OpFunctionCall %void %func %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..5e861d0
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,9 @@
+fn func(pointer : ptr<function, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : vec4<f32>;
+  func(&(F));
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..b71ee41
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x4<f32>;
+  func(&F[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..c071e39
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x4 F = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..c071e39
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,10 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  float2x4 F = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  func(F[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..0f200b2
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout vec4 pointer) {
+  pointer = vec4(0.0f);
+}
+
+void tint_symbol() {
+  mat2x4 F = mat2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  func(F[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..ecd2c7d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol() {
+  float2x4 F = float2x4(0.0f);
+  func(&(F[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..9daa630
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,46 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpName %F "F"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+%_ptr_Function_mat2v4float = OpTypePointer Function %mat2v4float
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %1 = OpTypeFunction %void %_ptr_Function_mat2v4float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %16 = OpConstantNull %int
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+         %20 = OpConstantNull %v4float
+         %21 = OpTypeFunction %void
+         %25 = OpConstantNull %mat2v4float
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %void None %1
+%pointer_base = OpFunctionParameter %_ptr_Function_mat2v4float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %13 = OpLabel
+         %17 = OpCompositeExtract %uint %pointer_indices 0
+         %19 = OpAccessChain %_ptr_Function_v4float %pointer_base %17
+               OpStore %19 %20
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %23 = OpLabel
+          %F = OpVariable %_ptr_Function_mat2v4float Function %25
+         %26 = OpFunctionCall %void %func_F_X %F %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..590aa4f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,11 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<function, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : mat2x4<f32>;
+  func(&(F[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..9351165
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+fn func(pointer : ptr<function, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  func(&F.i);
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..0e8b13b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  float4 i;
+};
+
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..0e8b13b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,14 @@
+struct str {
+  float4 i;
+};
+
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  str F = (str)0;
+  func(F.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..f56072d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+void func(inout vec4 pointer) {
+  pointer = vec4(0.0f);
+}
+
+void tint_symbol() {
+  str F = str(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+  func(F.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..b67035e
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  float4 i;
+};
+
+void func(thread float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol() {
+  str F = {};
+  func(&(F.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..19f521c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,41 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 23
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpName %F "F"
+               OpMemberDecorate %str 0 Offset 0
+       %void = OpTypeVoid
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+%_ptr_Function_str = OpTypePointer Function %str
+          %1 = OpTypeFunction %void %_ptr_Function_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+         %15 = OpConstantNull %v4float
+         %16 = OpTypeFunction %void
+         %20 = OpConstantNull %str
+   %func_F_i = OpFunction %void None %1
+    %pointer = OpFunctionParameter %_ptr_Function_str
+          %9 = OpLabel
+         %14 = OpAccessChain %_ptr_Function_v4float %pointer %uint_0
+               OpStore %14 %15
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %16
+         %18 = OpLabel
+          %F = OpVariable %_ptr_Function_str Function %20
+         %21 = OpFunctionCall %void %func_F_i %F
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..cf878a0
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/function/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+fn func(pointer : ptr<function, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  var F : str;
+  func(&(F.i));
+}
diff --git a/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl
new file mode 100644
index 0000000..0d7c2d8
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+fn func(pointer : ptr<private, array<i32, 4>>) {
+  *pointer = array<i32, 4>();
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P.arr);
+}
diff --git a/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..c6c407b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,16 @@
+struct str {
+  int arr[4];
+};
+
+void func(inout int pointer[4]) {
+  const int tint_symbol[4] = (int[4])0;
+  pointer = tint_symbol;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..c6c407b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,16 @@
+struct str {
+  int arr[4];
+};
+
+void func(inout int pointer[4]) {
+  const int tint_symbol[4] = (int[4])0;
+  pointer = tint_symbol;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P.arr);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..2b10ffb
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+void func(inout int pointer[4]) {
+  int tint_symbol_1[4] = int[4](0, 0, 0, 0);
+  pointer = tint_symbol_1;
+}
+
+str P = str(int[4](0, 0, 0, 0));
+void tint_symbol() {
+  func(P.arr);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..fbc443b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,31 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  tint_array<int, 4> arr;
+};
+
+void func(thread tint_array<int, 4>* const pointer) {
+  tint_array<int, 4> const tint_symbol_1 = tint_array<int, 4>{};
+  *(pointer) = tint_symbol_1;
+}
+
+kernel void tint_symbol() {
+  thread str tint_symbol_2 = {};
+  func(&(tint_symbol_2.arr));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..c2762ee
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,43 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 24
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %P "P"
+               OpName %func_F_arr "func_F_arr"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+%_ptr_Private_str = OpTypePointer Private %str
+          %8 = OpConstantNull %str
+          %P = OpVariable %_ptr_Private_str Private %8
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void %_ptr_Private_str
+     %uint_0 = OpConstant %uint 0
+%_ptr_Private__arr_int_uint_4 = OpTypePointer Private %_arr_int_uint_4
+         %18 = OpConstantNull %_arr_int_uint_4
+         %19 = OpTypeFunction %void
+ %func_F_arr = OpFunction %void None %9
+    %pointer = OpFunctionParameter %_ptr_Private_str
+         %13 = OpLabel
+         %17 = OpAccessChain %_ptr_Private__arr_int_uint_4 %pointer %uint_0
+               OpStore %17 %18
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %19
+         %21 = OpLabel
+         %22 = OpFunctionCall %void %func_F_arr %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..99958c7
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+fn func(pointer : ptr<private, array<i32, 4>>) {
+  *(pointer) = array<i32, 4>();
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P.arr));
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32.wgsl b/test/tint/ptr_ref/store/param/private/i32.wgsl
new file mode 100644
index 0000000..aad045c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, i32>) {
+  *pointer = 42;
+}
+
+var<private> P : i32;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P);
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..9085c4b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+static int P = 0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..9085c4b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+static int P = 0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..2c29881
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+int P = 0;
+void tint_symbol() {
+  func(P);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.msl
new file mode 100644
index 0000000..ead132a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread int* const pointer) {
+  *(pointer) = 42;
+}
+
+kernel void tint_symbol() {
+  thread int tint_symbol_1 = 0;
+  func(&(tint_symbol_1));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..867f836
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+        %int = OpTypeInt 32 1
+%_ptr_Private_int = OpTypePointer Private %int
+          %4 = OpConstantNull %int
+          %P = OpVariable %_ptr_Private_int Private %4
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void %_ptr_Private_int
+     %int_42 = OpConstant %int 42
+         %12 = OpTypeFunction %void
+       %func = OpFunction %void None %5
+    %pointer = OpFunctionParameter %_ptr_Private_int
+          %9 = OpLabel
+               OpStore %pointer %int_42
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %12
+         %14 = OpLabel
+         %15 = OpFunctionCall %void %func %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..fded040
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32.wgsl.expected.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, i32>) {
+  *(pointer) = 42;
+}
+
+var<private> P : i32;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P));
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl
new file mode 100644
index 0000000..2238584
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<private, i32>) {
+  *pointer = 42;
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P.i);
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..87c1651
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..87c1651
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  int i;
+};
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..0934fe1
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+void func(inout int pointer) {
+  pointer = 42;
+}
+
+str P = str(0);
+void tint_symbol() {
+  func(P.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..bcc4591
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  int i;
+};
+
+void func(thread int* const pointer) {
+  *(pointer) = 42;
+}
+
+kernel void tint_symbol() {
+  thread str tint_symbol_1 = {};
+  func(&(tint_symbol_1.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..cc49222
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,40 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 22
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %P "P"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+%_ptr_Private_str = OpTypePointer Private %str
+          %5 = OpConstantNull %str
+          %P = OpVariable %_ptr_Private_str Private %5
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void %_ptr_Private_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Private_int = OpTypePointer Private %int
+     %int_42 = OpConstant %int 42
+         %17 = OpTypeFunction %void
+   %func_F_i = OpFunction %void None %6
+    %pointer = OpFunctionParameter %_ptr_Private_str
+         %10 = OpLabel
+         %15 = OpAccessChain %_ptr_Private_int %pointer %uint_0
+               OpStore %15 %int_42
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %17
+         %19 = OpLabel
+         %20 = OpFunctionCall %void %func_F_i %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..7052c23
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<private, i32>) {
+  *(pointer) = 42;
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P.i));
+}
diff --git a/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl
new file mode 100644
index 0000000..2a9d4cd
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+fn func(pointer : ptr<private, str>) {
+  *pointer = str();
+}
+
+var<private> P : array<str, 4>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P[2]);
+}
diff --git a/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..ecc8458
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,16 @@
+struct str {
+  int i;
+};
+
+void func(inout str pointer) {
+  const str tint_symbol = (str)0;
+  pointer = tint_symbol;
+}
+
+static str P[4] = (str[4])0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..ecc8458
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,16 @@
+struct str {
+  int i;
+};
+
+void func(inout str pointer) {
+  const str tint_symbol = (str)0;
+  pointer = tint_symbol;
+}
+
+static str P[4] = (str[4])0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P[2]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..1791ced
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+void func(inout str pointer) {
+  str tint_symbol_1 = str(0);
+  pointer = tint_symbol_1;
+}
+
+str P[4] = str[4](str(0), str(0), str(0), str(0));
+void tint_symbol() {
+  func(P[2]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..d0f273b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,31 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  int i;
+};
+
+void func(thread str* const pointer) {
+  str const tint_symbol_1 = str{};
+  *(pointer) = tint_symbol_1;
+}
+
+kernel void tint_symbol() {
+  thread tint_array<str, 4> tint_symbol_2 = {};
+  func(&(tint_symbol_2[2]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..c502970
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,51 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %P "P"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+%_ptr_Private__arr_str_uint_4 = OpTypePointer Private %_arr_str_uint_4
+          %8 = OpConstantNull %_arr_str_uint_4
+          %P = OpVariable %_ptr_Private__arr_str_uint_4 Private %8
+       %void = OpTypeVoid
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %void %_ptr_Private__arr_str_uint_4 %_arr_uint_uint_1
+         %18 = OpConstantNull %int
+%_ptr_Private_str = OpTypePointer Private %str
+         %22 = OpConstantNull %str
+         %23 = OpTypeFunction %void
+     %uint_2 = OpConstant %uint 2
+         %29 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+   %func_F_X = OpFunction %void None %9
+%pointer_base = OpFunctionParameter %_ptr_Private__arr_str_uint_4
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %16 = OpLabel
+         %19 = OpCompositeExtract %uint %pointer_indices 0
+         %21 = OpAccessChain %_ptr_Private_str %pointer_base %19
+               OpStore %21 %22
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %23
+         %25 = OpLabel
+         %26 = OpFunctionCall %void %func_F_X %P %29
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..3c97892
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+fn func(pointer : ptr<private, str>) {
+  *(pointer) = str();
+}
+
+var<private> P : array<str, 4>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P[2]));
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..012a14d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec2<f32>>) {
+  *pointer = vec2<f32>();
+}
+
+var<private> P : mat2x2<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..6c9b6fc
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout float2 pointer) {
+  pointer = (0.0f).xx;
+}
+
+static float2x2 P = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..6c9b6fc
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout float2 pointer) {
+  pointer = (0.0f).xx;
+}
+
+static float2x2 P = float2x2(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..ea4cba1
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout vec2 pointer) {
+  pointer = vec2(0.0f);
+}
+
+mat2 P = mat2(0.0f, 0.0f, 0.0f, 0.0f);
+void tint_symbol() {
+  func(P[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..5ba30955
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread float2* const pointer) {
+  *(pointer) = float2(0.0f);
+}
+
+kernel void tint_symbol() {
+  thread float2x2 tint_symbol_1 = float2x2(0.0f);
+  func(&(tint_symbol_1[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..46d241c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,46 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+%_ptr_Private_mat2v2float = OpTypePointer Private %mat2v2float
+          %6 = OpConstantNull %mat2v2float
+          %P = OpVariable %_ptr_Private_mat2v2float Private %6
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %void %_ptr_Private_mat2v2float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %18 = OpConstantNull %int
+%_ptr_Private_v2float = OpTypePointer Private %v2float
+         %22 = OpConstantNull %v2float
+         %23 = OpTypeFunction %void
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %void None %7
+%pointer_base = OpFunctionParameter %_ptr_Private_mat2v2float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %19 = OpCompositeExtract %uint %pointer_indices 0
+         %21 = OpAccessChain %_ptr_Private_v2float %pointer_base %19
+               OpStore %21 %22
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %23
+         %25 = OpLabel
+         %26 = OpFunctionCall %void %func_F_X %P %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..fe087a1
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec2<f32>>) {
+  *(pointer) = vec2<f32>();
+}
+
+var<private> P : mat2x2<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl
new file mode 100644
index 0000000..5097807
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+var<private> P : vec4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P);
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..7e68214
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+static float4 P = float4(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..7e68214
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+static float4 P = float4(0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..a92e4e0
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout vec4 pointer) {
+  pointer = vec4(0.0f);
+}
+
+vec4 P = vec4(0.0f, 0.0f, 0.0f, 0.0f);
+void tint_symbol() {
+  func(P);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..1f3faf7
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol() {
+  thread float4 tint_symbol_1 = 0.0f;
+  func(&(tint_symbol_1));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..334b687
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func "func"
+               OpName %pointer "pointer"
+               OpName %main "main"
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+          %5 = OpConstantNull %v4float
+          %P = OpVariable %_ptr_Private_v4float Private %5
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void %_ptr_Private_v4float
+         %12 = OpTypeFunction %void
+       %func = OpFunction %void None %6
+    %pointer = OpFunctionParameter %_ptr_Private_v4float
+         %10 = OpLabel
+               OpStore %pointer %5
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %12
+         %14 = OpLabel
+         %15 = OpFunctionCall %void %func %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..328a6b7
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,10 @@
+fn func(pointer : ptr<private, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+var<private> P : vec4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P));
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..14e4bfa
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+var<private> P : mat2x4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..dcc5bde
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+static float2x4 P = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..dcc5bde
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+static float2x4 P = float2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P[1]);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..e231815
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,16 @@
+#version 310 es
+
+void func(inout vec4 pointer) {
+  pointer = vec4(0.0f);
+}
+
+mat2x4 P = mat2x4(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+void tint_symbol() {
+  func(P[1]);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..cbc5a43
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,13 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(thread float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol() {
+  thread float2x4 tint_symbol_1 = float2x4(0.0f);
+  func(&(tint_symbol_1[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..278d725
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,46 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %P "P"
+               OpName %func_F_X "func_F_X"
+               OpName %pointer_base "pointer_base"
+               OpName %pointer_indices "pointer_indices"
+               OpName %main "main"
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+%_ptr_Private_mat2v4float = OpTypePointer Private %mat2v4float
+          %6 = OpConstantNull %mat2v4float
+          %P = OpVariable %_ptr_Private_mat2v4float Private %6
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %void %_ptr_Private_mat2v4float %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %18 = OpConstantNull %int
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+         %22 = OpConstantNull %v4float
+         %23 = OpTypeFunction %void
+         %28 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_F_X = OpFunction %void None %7
+%pointer_base = OpFunctionParameter %_ptr_Private_mat2v4float
+%pointer_indices = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %19 = OpCompositeExtract %uint %pointer_indices 0
+         %21 = OpAccessChain %_ptr_Private_v4float %pointer_base %19
+               OpStore %21 %22
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %23
+         %25 = OpLabel
+         %26 = OpFunctionCall %void %func_F_X %P %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..3ba72c6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+fn func(pointer : ptr<private, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+var<private> P : mat2x4<f32>;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..bdc5cbc
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+fn func(pointer : ptr<private, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&P.i);
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..221d08a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  float4 i;
+};
+
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..221d08a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,15 @@
+struct str {
+  float4 i;
+};
+
+void func(inout float4 pointer) {
+  pointer = (0.0f).xxxx;
+}
+
+static str P = (str)0;
+
+[numthreads(1, 1, 1)]
+void main() {
+  func(P.i);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..afea0f0
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+void func(inout vec4 pointer) {
+  pointer = vec4(0.0f);
+}
+
+str P = str(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+void tint_symbol() {
+  func(P.i);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..3185a65
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,17 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  float4 i;
+};
+
+void func(thread float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol() {
+  thread str tint_symbol_1 = {};
+  func(&(tint_symbol_1.i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..5f41d63
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,41 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 23
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %P "P"
+               OpName %func_F_i "func_F_i"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpMemberDecorate %str 0 Offset 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+%_ptr_Private_str = OpTypePointer Private %str
+          %6 = OpConstantNull %str
+          %P = OpVariable %_ptr_Private_str Private %6
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void %_ptr_Private_str
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_Private_v4float = OpTypePointer Private %v4float
+         %17 = OpConstantNull %v4float
+         %18 = OpTypeFunction %void
+   %func_F_i = OpFunction %void None %7
+    %pointer = OpFunctionParameter %_ptr_Private_str
+         %11 = OpLabel
+         %16 = OpAccessChain %_ptr_Private_v4float %pointer %uint_0
+               OpStore %16 %17
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %18
+         %20 = OpLabel
+         %21 = OpFunctionCall %void %func_F_i %P
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..d4c15e1
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/private/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+fn func(pointer : ptr<private, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+var<private> P : str;
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(P.i));
+}
diff --git a/test/tint/ptr_ref/store/param/ptr.spvasm b/test/tint/ptr_ref/store/param/ptr.spvasm
deleted file mode 100644
index e750538..0000000
--- a/test/tint/ptr_ref/store/param/ptr.spvasm
+++ /dev/null
@@ -1,35 +0,0 @@
-; SPIR-V
-; Version: 1.3
-; Generator: Google Tint Compiler; 0
-; Bound: 18
-; Schema: 0
-               OpCapability Shader
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint GLCompute %main "main"
-               OpExecutionMode %main LocalSize 1 1 1
-               OpName %func "func"
-               OpName %value "value"
-               OpName %pointer "pointer"
-               OpName %main "main"
-               OpName %i "i"
-       %void = OpTypeVoid
-        %int = OpTypeInt 32 1
-%_ptr_Function_int = OpTypePointer Function %int
-          %1 = OpTypeFunction %void %int %_ptr_Function_int
-         %10 = OpTypeFunction %void
-    %int_123 = OpConstant %int 123
-         %15 = OpConstantNull %int
-       %func = OpFunction %void None %1
-      %value = OpFunctionParameter %int
-    %pointer = OpFunctionParameter %_ptr_Function_int
-          %8 = OpLabel
-               OpStore %pointer %value
-               OpReturn
-               OpFunctionEnd
-       %main = OpFunction %void None %10
-         %12 = OpLabel
-          %i = OpVariable %_ptr_Function_int Function %15
-               OpStore %i %int_123
-         %16 = OpFunctionCall %void %func %int_123 %i
-               OpReturn
-               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/ptr.spvasm.expected.glsl b/test/tint/ptr_ref/store/param/ptr.spvasm.expected.glsl
deleted file mode 100644
index b90efb1..0000000
--- a/test/tint/ptr_ref/store/param/ptr.spvasm.expected.glsl
+++ /dev/null
@@ -1,23 +0,0 @@
-#version 310 es
-
-void func(int value, inout int pointer) {
-  pointer = value;
-  return;
-}
-
-void main_1() {
-  int i = 0;
-  i = 123;
-  func(123, i);
-  return;
-}
-
-void tint_symbol() {
-  main_1();
-}
-
-layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
-void main() {
-  tint_symbol();
-  return;
-}
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl
new file mode 100644
index 0000000..e0e40eb
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn func(pointer : ptr<storage, array<i32, 4>, read_write>) {
+  *pointer = array<i32, 4>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S.arr);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..30148d5
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,21 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void tint_symbol(RWByteAddressBuffer buffer, uint offset, int value[4]) {
+  int array[4] = value;
+  {
+    for(uint i = 0u; (i < 4u); i = (i + 1u)) {
+      buffer.Store((offset + (i * 4u)), asuint(array[i]));
+    }
+  }
+}
+
+void func_S_arr() {
+  const int tint_symbol_2[4] = (int[4])0;
+  tint_symbol(S, 0u, tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S_arr();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..30148d5
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,21 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void tint_symbol(RWByteAddressBuffer buffer, uint offset, int value[4]) {
+  int array[4] = value;
+  {
+    for(uint i = 0u; (i < 4u); i = (i + 1u)) {
+      buffer.Store((offset + (i * 4u)), asuint(array[i]));
+    }
+  }
+}
+
+void func_S_arr() {
+  const int tint_symbol_2[4] = (int[4])0;
+  tint_symbol(S, 0u, tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S_arr();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..51fb051
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,24 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner;
+} S;
+
+void func_S_arr() {
+  int tint_symbol_1[4] = int[4](0, 0, 0, 0);
+  S.inner.arr = tint_symbol_1;
+}
+
+void tint_symbol() {
+  func_S_arr();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..14a5b86
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,30 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  /* 0x0000 */ tint_array<int, 4> arr;
+};
+
+void func(device tint_array<int, 4>* const pointer) {
+  tint_array<int, 4> const tint_symbol_1 = tint_array<int, 4>{};
+  *(pointer) = tint_symbol_1;
+}
+
+kernel void tint_symbol(device str* tint_symbol_2 [[buffer(0)]]) {
+  func(&((*(tint_symbol_2)).arr));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..51fda12
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,46 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 20
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %S "S"
+               OpName %func_S_arr "func_S_arr"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+    %S_block = OpTypeStruct %str
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer__arr_int_uint_4 = OpTypePointer StorageBuffer %_arr_int_uint_4
+         %16 = OpConstantNull %_arr_int_uint_4
+ %func_S_arr = OpFunction %void None %9
+         %12 = OpLabel
+         %15 = OpAccessChain %_ptr_StorageBuffer__arr_int_uint_4 %S %uint_0 %uint_0
+               OpStore %15 %16
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %9
+         %18 = OpLabel
+         %19 = OpFunctionCall %void %func_S_arr
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..5545a0d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn func(pointer : ptr<storage, array<i32, 4>, read_write>) {
+  *(pointer) = array<i32, 4>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S.arr));
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32.wgsl b/test/tint/ptr_ref/store/param/storage/i32.wgsl
new file mode 100644
index 0000000..8f1c6e9
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : i32;
+
+fn func(pointer : ptr<storage, i32, read_write>) {
+  *pointer = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..a907f4f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S() {
+  S.Store(0u, asuint(42));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..a907f4f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S() {
+  S.Store(0u, asuint(42));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..081c682
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.glsl
@@ -0,0 +1,19 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  int inner;
+} S;
+
+void func_S() {
+  S.inner = 42;
+}
+
+void tint_symbol() {
+  func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.msl
new file mode 100644
index 0000000..b2a8e0e
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(device int* const pointer) {
+  *(pointer) = 42;
+}
+
+kernel void tint_symbol(device int* tint_symbol_1 [[buffer(0)]]) {
+  func(tint_symbol_1);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..0d849ef
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.spvasm
@@ -0,0 +1,39 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 17
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+    %S_block = OpTypeStruct %int
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+          %5 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+     %int_42 = OpConstant %int 42
+     %func_S = OpFunction %void None %5
+          %8 = OpLabel
+         %12 = OpAccessChain %_ptr_StorageBuffer_int %S %uint_0
+               OpStore %12 %int_42
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %5
+         %15 = OpLabel
+         %16 = OpFunctionCall %void %func_S
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..6ed869b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : i32;
+
+fn func(pointer : ptr<storage, i32, read_write>) {
+  *(pointer) = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S));
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl
new file mode 100644
index 0000000..f68d27c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn func(pointer : ptr<storage, i32, read_write>) {
+  *pointer = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S.i);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..d020bf6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_i() {
+  S.Store(0u, asuint(42));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..d020bf6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_i() {
+  S.Store(0u, asuint(42));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..1552104
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner;
+} S;
+
+void func_S_i() {
+  S.inner.i = 42;
+}
+
+void tint_symbol() {
+  func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..a708f66
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,16 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  /* 0x0000 */ int i;
+};
+
+void func(device int* const pointer) {
+  *(pointer) = 42;
+}
+
+kernel void tint_symbol(device str* tint_symbol_1 [[buffer(0)]]) {
+  func(&((*(tint_symbol_1)).i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..042d5b4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,43 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+    %S_block = OpTypeStruct %str
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_int = OpTypePointer StorageBuffer %int
+     %int_42 = OpConstant %int 42
+   %func_S_i = OpFunction %void None %6
+          %9 = OpLabel
+         %13 = OpAccessChain %_ptr_StorageBuffer_int %S %uint_0 %uint_0
+               OpStore %13 %int_42
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %6
+         %16 = OpLabel
+         %17 = OpFunctionCall %void %func_S_i
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..6a35569
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn func(pointer : ptr<storage, i32, read_write>) {
+  *(pointer) = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl
new file mode 100644
index 0000000..f13db83
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+@group(0) @binding(0) var<storage, read_write> S : array<str, 4>;
+
+fn func(pointer : ptr<storage, str, read_write>) {
+  *pointer = str();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S[2]);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..b51e632
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,21 @@
+struct str {
+  int i;
+};
+
+RWByteAddressBuffer S : register(u0, space0);
+
+void tint_symbol(RWByteAddressBuffer buffer, uint offset, str value) {
+  buffer.Store((offset + 0u), asuint(value.i));
+}
+
+void func_S_X(uint pointer[1]) {
+  const str tint_symbol_2 = (str)0;
+  tint_symbol(S, (4u * pointer[0]), tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_3[1] = {2u};
+  func_S_X(tint_symbol_3);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..b51e632
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,21 @@
+struct str {
+  int i;
+};
+
+RWByteAddressBuffer S : register(u0, space0);
+
+void tint_symbol(RWByteAddressBuffer buffer, uint offset, str value) {
+  buffer.Store((offset + 0u), asuint(value.i));
+}
+
+void func_S_X(uint pointer[1]) {
+  const str tint_symbol_2 = (str)0;
+  tint_symbol(S, (4u * pointer[0]), tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_3[1] = {2u};
+  func_S_X(tint_symbol_3);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..55c82f8
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,25 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner[4];
+} S;
+
+void func_S_X(uint pointer[1]) {
+  str tint_symbol_1 = str(0);
+  S.inner[pointer[0]] = tint_symbol_1;
+}
+
+void tint_symbol() {
+  uint tint_symbol_2[1] = uint[1](2u);
+  func_S_X(tint_symbol_2);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..3119e7a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,30 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  /* 0x0000 */ int i;
+};
+
+void func(device str* const pointer) {
+  str const tint_symbol_1 = str{};
+  *(pointer) = tint_symbol_1;
+}
+
+kernel void tint_symbol(device tint_array<str, 4>* tint_symbol_2 [[buffer(0)]]) {
+  func(&((*(tint_symbol_2))[2]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..190781a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,56 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 28
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+       %uint = OpTypeInt 32 0
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+    %S_block = OpTypeStruct %_arr_str_uint_4
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %void %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+         %17 = OpConstantNull %int
+%_ptr_StorageBuffer_str = OpTypePointer StorageBuffer %str
+         %21 = OpConstantNull %str
+         %22 = OpTypeFunction %void
+     %uint_2 = OpConstant %uint 2
+         %27 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+   %func_S_X = OpFunction %void None %9
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer 0
+         %20 = OpAccessChain %_ptr_StorageBuffer_str %S %uint_0 %18
+               OpStore %20 %21
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %24 = OpLabel
+         %25 = OpFunctionCall %void %func_S_X %27
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..90db206
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+@group(0) @binding(0) var<storage, read_write> S : array<str, 4>;
+
+fn func(pointer : ptr<storage, str, read_write>) {
+  *(pointer) = str();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S[2]));
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..d69899b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : mat2x2<f32>;
+
+fn func(pointer : ptr<storage, vec2<f32>, read_write>) {
+  *pointer = vec2<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..c2a926b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,12 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_X(uint pointer[1]) {
+  S.Store2((8u * pointer[0]), asuint((0.0f).xx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..c2a926b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,12 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_X(uint pointer[1]) {
+  S.Store2((8u * pointer[0]), asuint((0.0f).xx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..ea1f5fc
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  mat2 inner;
+} S;
+
+void func_S_X(uint pointer[1]) {
+  S.inner[pointer[0]] = vec2(0.0f);
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](1u);
+  func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..fb8b603
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(device float2* const pointer) {
+  *(pointer) = float2(0.0f);
+}
+
+kernel void tint_symbol(device float2x2* tint_symbol_1 [[buffer(0)]]) {
+  func(&((*(tint_symbol_1))[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..0b294a2
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,53 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 27
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %S_block 0 ColMajor
+               OpMemberDecorate %S_block 0 MatrixStride 8
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+    %S_block = OpTypeStruct %mat2v2float
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %void %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+        %int = OpTypeInt 32 1
+         %17 = OpConstantNull %int
+%_ptr_StorageBuffer_v2float = OpTypePointer StorageBuffer %v2float
+         %21 = OpConstantNull %v2float
+         %22 = OpTypeFunction %void
+         %26 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_S_X = OpFunction %void None %7
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer 0
+         %20 = OpAccessChain %_ptr_StorageBuffer_v2float %S %uint_0 %18
+               OpStore %20 %21
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %24 = OpLabel
+         %25 = OpFunctionCall %void %func_S_X %26
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..d31440c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : mat2x2<f32>;
+
+fn func(pointer : ptr<storage, vec2<f32>, read_write>) {
+  *(pointer) = vec2<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl
new file mode 100644
index 0000000..b76e544
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : vec4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>, read_write>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..7343fc8
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S() {
+  S.Store4(0u, asuint((0.0f).xxxx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..7343fc8
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S() {
+  S.Store4(0u, asuint((0.0f).xxxx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..4a01502
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,19 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  vec4 inner;
+} S;
+
+void func_S() {
+  S.inner = vec4(0.0f);
+}
+
+void tint_symbol() {
+  func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..e727b7c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(device float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol(device float4* tint_symbol_1 [[buffer(0)]]) {
+  func(tint_symbol_1);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d8bae29
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,40 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 18
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+    %S_block = OpTypeStruct %v4float
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+          %6 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
+         %14 = OpConstantNull %v4float
+     %func_S = OpFunction %void None %6
+          %9 = OpLabel
+         %13 = OpAccessChain %_ptr_StorageBuffer_v4float %S %uint_0
+               OpStore %13 %14
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %6
+         %16 = OpLabel
+         %17 = OpFunctionCall %void %func_S
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..3a10bfb
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : vec4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>, read_write>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S));
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..82b69e9
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : mat2x4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>, read_write>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..a345b7a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,12 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_X(uint pointer[1]) {
+  S.Store4((16u * pointer[0]), asuint((0.0f).xxxx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..a345b7a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,12 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_X(uint pointer[1]) {
+  S.Store4((16u * pointer[0]), asuint((0.0f).xxxx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  const uint tint_symbol_1[1] = {1u};
+  func_S_X(tint_symbol_1);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..51898ee
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  mat2x4 inner;
+} S;
+
+void func_S_X(uint pointer[1]) {
+  S.inner[pointer[0]] = vec4(0.0f);
+}
+
+void tint_symbol() {
+  uint tint_symbol_1[1] = uint[1](1u);
+  func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..51641a3
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,12 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(device float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol(device float2x4* tint_symbol_1 [[buffer(0)]]) {
+  func(&((*(tint_symbol_1))[1]));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..db5ca8a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,53 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 27
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %S_block 0 ColMajor
+               OpMemberDecorate %S_block 0 MatrixStride 16
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+    %S_block = OpTypeStruct %mat2v4float
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %7 = OpTypeFunction %void %_arr_uint_uint_1
+     %uint_0 = OpConstant %uint 0
+        %int = OpTypeInt 32 1
+         %17 = OpConstantNull %int
+%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
+         %21 = OpConstantNull %v4float
+         %22 = OpTypeFunction %void
+         %26 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+   %func_S_X = OpFunction %void None %7
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %14 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer 0
+         %20 = OpAccessChain %_ptr_StorageBuffer_v4float %S %uint_0 %18
+               OpStore %20 %21
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %22
+         %24 = OpLabel
+         %25 = OpFunctionCall %void %func_S_X %26
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..822fba4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+@group(0) @binding(0) var<storage, read_write> S : mat2x4<f32>;
+
+fn func(pointer : ptr<storage, vec4<f32>, read_write>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..c49e789
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn func(pointer : ptr<storage, vec4<f32>, read_write>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S.i);
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..d98c3c6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_i() {
+  S.Store4(0u, asuint((0.0f).xxxx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..d98c3c6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,11 @@
+RWByteAddressBuffer S : register(u0, space0);
+
+void func_S_i() {
+  S.Store4(0u, asuint((0.0f).xxxx));
+}
+
+[numthreads(1, 1, 1)]
+void main() {
+  func_S_i();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..314e7d4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+layout(binding = 0, std430) buffer S_block_ssbo {
+  str inner;
+} S;
+
+void func_S_i() {
+  S.inner.i = vec4(0.0f);
+}
+
+void tint_symbol() {
+  func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol();
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..f4b522f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,16 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  /* 0x0000 */ float4 i;
+};
+
+void func(device float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+kernel void tint_symbol(device str* tint_symbol_1 [[buffer(0)]]) {
+  func(&((*(tint_symbol_1)).i));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..12a8253
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,44 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %S_block "S_block"
+               OpMemberName %S_block 0 "inner"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main "main"
+               OpDecorate %S_block Block
+               OpMemberDecorate %S_block 0 Offset 0
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %S DescriptorSet 0
+               OpDecorate %S Binding 0
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+    %S_block = OpTypeStruct %str
+%_ptr_StorageBuffer_S_block = OpTypePointer StorageBuffer %S_block
+          %S = OpVariable %_ptr_StorageBuffer_S_block StorageBuffer
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+       %uint = OpTypeInt 32 0
+     %uint_0 = OpConstant %uint 0
+%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float
+         %15 = OpConstantNull %v4float
+   %func_S_i = OpFunction %void None %7
+         %10 = OpLabel
+         %14 = OpAccessChain %_ptr_StorageBuffer_v4float %S %uint_0 %uint_0
+               OpStore %14 %15
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %7
+         %17 = OpLabel
+         %18 = OpFunctionCall %void %func_S_i
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..89c0bc3
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/storage/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+@group(0) @binding(0) var<storage, read_write> S : str;
+
+fn func(pointer : ptr<storage, vec4<f32>, read_write>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl
new file mode 100644
index 0000000..4bf2e36
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+};
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, array<i32, 4>>) {
+  *pointer = array<i32, 4>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S.arr);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..7a28ceb
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,31 @@
+struct str {
+  int arr[4];
+};
+
+groupshared str S;
+
+void func_S_arr() {
+  const int tint_symbol_2[4] = (int[4])0;
+  S.arr = tint_symbol_2;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i = idx;
+      S.arr[i] = 0;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S_arr();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..7a28ceb
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,31 @@
+struct str {
+  int arr[4];
+};
+
+groupshared str S;
+
+void func_S_arr() {
+  const int tint_symbol_2[4] = (int[4])0;
+  S.arr = tint_symbol_2;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i = idx;
+      S.arr[i] = 0;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S_arr();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..a551b4b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.glsl
@@ -0,0 +1,28 @@
+#version 310 es
+
+struct str {
+  int arr[4];
+};
+
+shared str S;
+void func_S_arr() {
+  int tint_symbol_1[4] = int[4](0, 0, 0, 0);
+  S.arr = tint_symbol_1;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      uint i = idx;
+      S.arr[i] = 0;
+    }
+  }
+  barrier();
+  func_S_arr();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..8652c09
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.msl
@@ -0,0 +1,40 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  tint_array<int, 4> arr;
+};
+
+void func(threadgroup tint_array<int, 4>* const pointer) {
+  tint_array<int, 4> const tint_symbol_1 = tint_array<int, 4>{};
+  *(pointer) = tint_symbol_1;
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup str* const tint_symbol_2) {
+  for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    uint const i = idx;
+    (*(tint_symbol_2)).arr[i] = 0;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(&((*(tint_symbol_2)).arr));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup str tint_symbol_3;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_3));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..d6080c8
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,88 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 50
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "arr"
+               OpName %S "S"
+               OpName %func_S_arr "func_S_arr"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %idx "idx"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_int_uint_4 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+     %uint_4 = OpConstant %uint 4
+%_arr_int_uint_4 = OpTypeArray %int %uint_4
+        %str = OpTypeStruct %_arr_int_uint_4
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+          %S = OpVariable %_ptr_Workgroup_str Workgroup
+       %void = OpTypeVoid
+         %10 = OpTypeFunction %void
+     %uint_0 = OpConstant %uint 0
+%_ptr_Workgroup__arr_int_uint_4 = OpTypePointer Workgroup %_arr_int_uint_4
+         %17 = OpConstantNull %_arr_int_uint_4
+         %18 = OpTypeFunction %void %uint
+%_ptr_Function_uint = OpTypePointer Function %uint
+         %24 = OpConstantNull %uint
+       %bool = OpTypeBool
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+         %38 = OpConstantNull %int
+     %uint_1 = OpConstant %uint 1
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+ %func_S_arr = OpFunction %void None %10
+         %13 = OpLabel
+         %16 = OpAccessChain %_ptr_Workgroup__arr_int_uint_4 %S %uint_0
+               OpStore %16 %17
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %18
+%local_invocation_index = OpFunctionParameter %uint
+         %21 = OpLabel
+        %idx = OpVariable %_ptr_Function_uint Function %24
+               OpStore %idx %local_invocation_index
+               OpBranch %25
+         %25 = OpLabel
+               OpLoopMerge %26 %27 None
+               OpBranch %28
+         %28 = OpLabel
+         %30 = OpLoad %uint %idx
+         %31 = OpULessThan %bool %30 %uint_4
+         %29 = OpLogicalNot %bool %31
+               OpSelectionMerge %33 None
+               OpBranchConditional %29 %34 %33
+         %34 = OpLabel
+               OpBranch %26
+         %33 = OpLabel
+         %35 = OpLoad %uint %idx
+         %37 = OpAccessChain %_ptr_Workgroup_int %S %uint_0 %35
+               OpStore %37 %38
+               OpBranch %27
+         %27 = OpLabel
+         %39 = OpLoad %uint %idx
+         %41 = OpIAdd %uint %39 %uint_1
+               OpStore %idx %41
+               OpBranch %25
+         %26 = OpLabel
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %45 = OpFunctionCall %void %func_S_arr
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %10
+         %47 = OpLabel
+         %49 = OpLoad %uint %local_invocation_index_1
+         %48 = OpFunctionCall %void %main_inner %49
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..b6f7aeb
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/array_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  arr : array<i32, 4>,
+}
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, array<i32, 4>>) {
+  *(pointer) = array<i32, 4>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S.arr));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32.wgsl b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl
new file mode 100644
index 0000000..9d7d66a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : i32;
+
+fn func(pointer : ptr<workgroup, i32>) {
+  *pointer = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8ed52c4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,23 @@
+groupshared int S;
+
+void func_S() {
+  S = 42;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = 0;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8ed52c4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,23 @@
+groupshared int S;
+
+void func_S() {
+  S = 42;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = 0;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.glsl
new file mode 100644
index 0000000..3b35642
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+shared int S;
+void func_S() {
+  S = 42;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = 0;
+  }
+  barrier();
+  func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.msl
new file mode 100644
index 0000000..f6a36ce
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.msl
@@ -0,0 +1,21 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(threadgroup int* const pointer) {
+  *(pointer) = 42;
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup int* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = 0;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(tint_symbol_1);
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup int tint_symbol_2;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_2));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.spvasm
new file mode 100644
index 0000000..a471fe4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.spvasm
@@ -0,0 +1,48 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+          %S = OpVariable %_ptr_Workgroup_int Workgroup
+       %void = OpTypeVoid
+          %7 = OpTypeFunction %void
+     %int_42 = OpConstant %int 42
+         %12 = OpTypeFunction %void %uint
+         %16 = OpConstantNull %int
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+     %func_S = OpFunction %void None %7
+         %10 = OpLabel
+               OpStore %S %int_42
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %12
+%local_invocation_index = OpFunctionParameter %uint
+         %15 = OpLabel
+               OpStore %S %16
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %20 = OpFunctionCall %void %func_S
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %7
+         %22 = OpLabel
+         %24 = OpLoad %uint %local_invocation_index_1
+         %23 = OpFunctionCall %void %main_inner %24
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.wgsl
new file mode 100644
index 0000000..3bb4d84
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : i32;
+
+fn func(pointer : ptr<workgroup, i32>) {
+  *(pointer) = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl
new file mode 100644
index 0000000..3c3209f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, i32>) {
+  *pointer = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S.i);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..a8ddec6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  int i;
+};
+
+groupshared str S;
+
+void func_S_i() {
+  S.i = 42;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..a8ddec6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  int i;
+};
+
+groupshared str S;
+
+void func_S_i() {
+  S.i = 42;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..dc3ac54
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,25 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+shared str S;
+void func_S_i() {
+  S.i = 42;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    str tint_symbol_1 = str(0);
+    S = tint_symbol_1;
+  }
+  barrier();
+  func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..419a502
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.msl
@@ -0,0 +1,26 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  int i;
+};
+
+void func(threadgroup int* const pointer) {
+  *(pointer) = 42;
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup str* const tint_symbol_2) {
+  {
+    str const tint_symbol_1 = str{};
+    *(tint_symbol_2) = tint_symbol_1;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(&((*(tint_symbol_2)).i));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup str tint_symbol_3;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_3));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..5a4b764
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,55 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 29
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+          %S = OpVariable %_ptr_Workgroup_str Workgroup
+       %void = OpTypeVoid
+          %8 = OpTypeFunction %void
+     %uint_0 = OpConstant %uint 0
+%_ptr_Workgroup_int = OpTypePointer Workgroup %int
+     %int_42 = OpConstant %int 42
+         %16 = OpTypeFunction %void %uint
+         %20 = OpConstantNull %str
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+   %func_S_i = OpFunction %void None %8
+         %11 = OpLabel
+         %14 = OpAccessChain %_ptr_Workgroup_int %S %uint_0
+               OpStore %14 %int_42
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %16
+%local_invocation_index = OpFunctionParameter %uint
+         %19 = OpLabel
+               OpStore %S %20
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %24 = OpFunctionCall %void %func_S_i
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %8
+         %26 = OpLabel
+         %28 = OpLoad %uint %local_invocation_index_1
+         %27 = OpFunctionCall %void %main_inner %28
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..642e2a5
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/i32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, i32>) {
+  *(pointer) = 42;
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S.i));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl
new file mode 100644
index 0000000..5fbef20
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+};
+
+var<workgroup> S : array<str, 4>;
+
+fn func(pointer : ptr<workgroup, str>) {
+  *pointer = str();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S[2]);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..b3b8183
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.dxc.hlsl
@@ -0,0 +1,33 @@
+struct str {
+  int i;
+};
+
+groupshared str S[4];
+
+void func_S_X(uint pointer[1]) {
+  const str tint_symbol_2 = (str)0;
+  S[pointer[0]] = tint_symbol_2;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i_1 = idx;
+      const str tint_symbol_3 = (str)0;
+      S[i_1] = tint_symbol_3;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_4[1] = {2u};
+  func_S_X(tint_symbol_4);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..b3b8183
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.fxc.hlsl
@@ -0,0 +1,33 @@
+struct str {
+  int i;
+};
+
+groupshared str S[4];
+
+void func_S_X(uint pointer[1]) {
+  const str tint_symbol_2 = (str)0;
+  S[pointer[0]] = tint_symbol_2;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      const uint i_1 = idx;
+      const str tint_symbol_3 = (str)0;
+      S[i_1] = tint_symbol_3;
+    }
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_4[1] = {2u};
+  func_S_X(tint_symbol_4);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.glsl
new file mode 100644
index 0000000..76b2e7d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.glsl
@@ -0,0 +1,30 @@
+#version 310 es
+
+struct str {
+  int i;
+};
+
+shared str S[4];
+void func_S_X(uint pointer[1]) {
+  str tint_symbol_1 = str(0);
+  S[pointer[0]] = tint_symbol_1;
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+      uint i_1 = idx;
+      str tint_symbol_2 = str(0);
+      S[i_1] = tint_symbol_2;
+    }
+  }
+  barrier();
+  uint tint_symbol_3[1] = uint[1](2u);
+  func_S_X(tint_symbol_3);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.msl
new file mode 100644
index 0000000..7520035
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.msl
@@ -0,0 +1,41 @@
+#include <metal_stdlib>
+
+using namespace metal;
+
+template<typename T, size_t N>
+struct tint_array {
+    const constant T& operator[](size_t i) const constant { return elements[i]; }
+    device T& operator[](size_t i) device { return elements[i]; }
+    const device T& operator[](size_t i) const device { return elements[i]; }
+    thread T& operator[](size_t i) thread { return elements[i]; }
+    const thread T& operator[](size_t i) const thread { return elements[i]; }
+    threadgroup T& operator[](size_t i) threadgroup { return elements[i]; }
+    const threadgroup T& operator[](size_t i) const threadgroup { return elements[i]; }
+    T elements[N];
+};
+
+struct str {
+  int i;
+};
+
+void func(threadgroup str* const pointer) {
+  str const tint_symbol_1 = str{};
+  *(pointer) = tint_symbol_1;
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup tint_array<str, 4>* const tint_symbol_3) {
+  for(uint idx = local_invocation_index; (idx < 4u); idx = (idx + 1u)) {
+    uint const i_1 = idx;
+    str const tint_symbol_2 = str{};
+    (*(tint_symbol_3))[i_1] = tint_symbol_2;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(&((*(tint_symbol_3))[2]));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup tint_array<str, 4> tint_symbol_4;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_4));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.spvasm
new file mode 100644
index 0000000..61ef214
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.spvasm
@@ -0,0 +1,93 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 53
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %idx "idx"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+               OpDecorate %_arr_str_uint_4 ArrayStride 4
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+        %int = OpTypeInt 32 1
+        %str = OpTypeStruct %int
+     %uint_4 = OpConstant %uint 4
+%_arr_str_uint_4 = OpTypeArray %str %uint_4
+%_ptr_Workgroup__arr_str_uint_4 = OpTypePointer Workgroup %_arr_str_uint_4
+          %S = OpVariable %_ptr_Workgroup__arr_str_uint_4 Workgroup
+       %void = OpTypeVoid
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+         %10 = OpTypeFunction %void %_arr_uint_uint_1
+         %17 = OpConstantNull %int
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+         %21 = OpConstantNull %str
+         %22 = OpTypeFunction %void %uint
+%_ptr_Function_uint = OpTypePointer Function %uint
+         %28 = OpConstantNull %uint
+       %bool = OpTypeBool
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %47 = OpConstantComposite %_arr_uint_uint_1 %uint_2
+         %48 = OpTypeFunction %void
+   %func_S_X = OpFunction %void None %10
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %16 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer 0
+         %20 = OpAccessChain %_ptr_Workgroup_str %S %18
+               OpStore %20 %21
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %22
+%local_invocation_index = OpFunctionParameter %uint
+         %25 = OpLabel
+        %idx = OpVariable %_ptr_Function_uint Function %28
+               OpStore %idx %local_invocation_index
+               OpBranch %29
+         %29 = OpLabel
+               OpLoopMerge %30 %31 None
+               OpBranch %32
+         %32 = OpLabel
+         %34 = OpLoad %uint %idx
+         %35 = OpULessThan %bool %34 %uint_4
+         %33 = OpLogicalNot %bool %35
+               OpSelectionMerge %37 None
+               OpBranchConditional %33 %38 %37
+         %38 = OpLabel
+               OpBranch %30
+         %37 = OpLabel
+         %39 = OpLoad %uint %idx
+         %40 = OpAccessChain %_ptr_Workgroup_str %S %39
+               OpStore %40 %21
+               OpBranch %31
+         %31 = OpLabel
+         %41 = OpLoad %uint %idx
+         %42 = OpIAdd %uint %41 %uint_1
+               OpStore %idx %42
+               OpBranch %29
+         %30 = OpLabel
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %46 = OpFunctionCall %void %func_S_X %47
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %48
+         %50 = OpLabel
+         %52 = OpLoad %uint %local_invocation_index_1
+         %51 = OpFunctionCall %void %main_inner %52
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.wgsl
new file mode 100644
index 0000000..8177527
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/struct_in_array.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : i32,
+}
+
+var<workgroup> S : array<str, 4>;
+
+fn func(pointer : ptr<workgroup, str>) {
+  *(pointer) = str();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S[2]));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl
new file mode 100644
index 0000000..2c25bbf
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x2<f32>;
+
+fn func(pointer : ptr<workgroup, vec2<f32>>) {
+  *pointer = vec2<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..642e72c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.dxc.hlsl
@@ -0,0 +1,31 @@
+void set_vector_float2x2(inout float2x2 mat, int col, float2 val) {
+  switch (col) {
+    case 0: mat[0] = val; break;
+    case 1: mat[1] = val; break;
+  }
+}
+
+groupshared float2x2 S;
+
+void func_S_X(uint pointer[1]) {
+  set_vector_float2x2(S, pointer[0], (0.0f).xx);
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x2((0.0f).xx, (0.0f).xx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..642e72c
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.fxc.hlsl
@@ -0,0 +1,31 @@
+void set_vector_float2x2(inout float2x2 mat, int col, float2 val) {
+  switch (col) {
+    case 0: mat[0] = val; break;
+    case 1: mat[1] = val; break;
+  }
+}
+
+groupshared float2x2 S;
+
+void func_S_X(uint pointer[1]) {
+  set_vector_float2x2(S, pointer[0], (0.0f).xx);
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x2((0.0f).xx, (0.0f).xx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.glsl
new file mode 100644
index 0000000..6d63b6a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+shared mat2 S;
+void func_S_X(uint pointer[1]) {
+  S[pointer[0]] = vec2(0.0f);
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = mat2(vec2(0.0f), vec2(0.0f));
+  }
+  barrier();
+  uint tint_symbol_1[1] = uint[1](1u);
+  func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.msl
new file mode 100644
index 0000000..9e04a33
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.msl
@@ -0,0 +1,25 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_4 {
+  float2x2 S;
+};
+
+void func(threadgroup float2* const pointer) {
+  *(pointer) = float2(0.0f);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup float2x2* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = float2x2(float2(0.0f), float2(0.0f));
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(&((*(tint_symbol_1))[1]));
+}
+
+kernel void tint_symbol(threadgroup tint_symbol_4* tint_symbol_3 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float2x2* const tint_symbol_2 = &((*(tint_symbol_3)).S);
+  tint_symbol_inner(local_invocation_index, tint_symbol_2);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.spvasm
new file mode 100644
index 0000000..8246c4a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.spvasm
@@ -0,0 +1,62 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 37
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v2float = OpTypeVector %float 2
+%mat2v2float = OpTypeMatrix %v2float 2
+%_ptr_Workgroup_mat2v2float = OpTypePointer Workgroup %mat2v2float
+          %S = OpVariable %_ptr_Workgroup_mat2v2float Workgroup
+       %void = OpTypeVoid
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %void %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %17 = OpConstantNull %int
+%_ptr_Workgroup_v2float = OpTypePointer Workgroup %v2float
+         %21 = OpConstantNull %v2float
+         %22 = OpTypeFunction %void %uint
+         %26 = OpConstantNull %mat2v2float
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %31 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+         %32 = OpTypeFunction %void
+   %func_S_X = OpFunction %void None %9
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer 0
+         %20 = OpAccessChain %_ptr_Workgroup_v2float %S %18
+               OpStore %20 %21
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %22
+%local_invocation_index = OpFunctionParameter %uint
+         %25 = OpLabel
+               OpStore %S %26
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %30 = OpFunctionCall %void %func_S_X %31
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %32
+         %34 = OpLabel
+         %36 = OpLoad %uint %local_invocation_index_1
+         %35 = OpFunctionCall %void %main_inner %36
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.wgsl
new file mode 100644
index 0000000..7f66524
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec2_f32_in_mat2x2.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x2<f32>;
+
+fn func(pointer : ptr<workgroup, vec2<f32>>) {
+  *(pointer) = vec2<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl
new file mode 100644
index 0000000..382f211
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : vec4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..abc201b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.dxc.hlsl
@@ -0,0 +1,23 @@
+groupshared float4 S;
+
+void func_S() {
+  S = (0.0f).xxxx;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = (0.0f).xxxx;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..abc201b
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.fxc.hlsl
@@ -0,0 +1,23 @@
+groupshared float4 S;
+
+void func_S() {
+  S = (0.0f).xxxx;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = (0.0f).xxxx;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.glsl
new file mode 100644
index 0000000..f542b63
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+#version 310 es
+
+shared vec4 S;
+void func_S() {
+  S = vec4(0.0f);
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = vec4(0.0f);
+  }
+  barrier();
+  func_S();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.msl
new file mode 100644
index 0000000..8489e3f
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.msl
@@ -0,0 +1,21 @@
+#include <metal_stdlib>
+
+using namespace metal;
+void func(threadgroup float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup float4* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = float4(0.0f);
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(tint_symbol_1);
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float4 tint_symbol_2;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_2));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.spvasm
new file mode 100644
index 0000000..d657955
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.spvasm
@@ -0,0 +1,48 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 25
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S "func_S"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
+          %S = OpVariable %_ptr_Workgroup_v4float Workgroup
+       %void = OpTypeVoid
+          %8 = OpTypeFunction %void
+         %12 = OpConstantNull %v4float
+         %13 = OpTypeFunction %void %uint
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+     %func_S = OpFunction %void None %8
+         %11 = OpLabel
+               OpStore %S %12
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %13
+%local_invocation_index = OpFunctionParameter %uint
+         %16 = OpLabel
+               OpStore %S %12
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %20 = OpFunctionCall %void %func_S
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %8
+         %22 = OpLabel
+         %24 = OpLoad %uint %local_invocation_index_1
+         %23 = OpFunctionCall %void %main_inner %24
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.wgsl
new file mode 100644
index 0000000..1244c4a
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : vec4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl
new file mode 100644
index 0000000..8143ef6
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S[1]);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..bceda5e
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.dxc.hlsl
@@ -0,0 +1,31 @@
+void set_vector_float2x4(inout float2x4 mat, int col, float4 val) {
+  switch (col) {
+    case 0: mat[0] = val; break;
+    case 1: mat[1] = val; break;
+  }
+}
+
+groupshared float2x4 S;
+
+void func_S_X(uint pointer[1]) {
+  set_vector_float2x4(S, pointer[0], (0.0f).xxxx);
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x4((0.0f).xxxx, (0.0f).xxxx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..bceda5e
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.fxc.hlsl
@@ -0,0 +1,31 @@
+void set_vector_float2x4(inout float2x4 mat, int col, float4 val) {
+  switch (col) {
+    case 0: mat[0] = val; break;
+    case 1: mat[1] = val; break;
+  }
+}
+
+groupshared float2x4 S;
+
+void func_S_X(uint pointer[1]) {
+  set_vector_float2x4(S, pointer[0], (0.0f).xxxx);
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    S = float2x4((0.0f).xxxx, (0.0f).xxxx);
+  }
+  GroupMemoryBarrierWithGroupSync();
+  const uint tint_symbol_2[1] = {1u};
+  func_S_X(tint_symbol_2);
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.glsl
new file mode 100644
index 0000000..76afcaf
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+#version 310 es
+
+shared mat2x4 S;
+void func_S_X(uint pointer[1]) {
+  S[pointer[0]] = vec4(0.0f);
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    S = mat2x4(vec4(0.0f), vec4(0.0f));
+  }
+  barrier();
+  uint tint_symbol_1[1] = uint[1](1u);
+  func_S_X(tint_symbol_1);
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.msl
new file mode 100644
index 0000000..0f5241d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.msl
@@ -0,0 +1,25 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct tint_symbol_4 {
+  float2x4 S;
+};
+
+void func(threadgroup float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup float2x4* const tint_symbol_1) {
+  {
+    *(tint_symbol_1) = float2x4(float4(0.0f), float4(0.0f));
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(&((*(tint_symbol_1))[1]));
+}
+
+kernel void tint_symbol(threadgroup tint_symbol_4* tint_symbol_3 [[threadgroup(0)]], uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup float2x4* const tint_symbol_2 = &((*(tint_symbol_3)).S);
+  tint_symbol_inner(local_invocation_index, tint_symbol_2);
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.spvasm
new file mode 100644
index 0000000..a007c93
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.spvasm
@@ -0,0 +1,62 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 37
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %S "S"
+               OpName %func_S_X "func_S_X"
+               OpName %pointer "pointer"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpDecorate %_arr_uint_uint_1 ArrayStride 4
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%mat2v4float = OpTypeMatrix %v4float 2
+%_ptr_Workgroup_mat2v4float = OpTypePointer Workgroup %mat2v4float
+          %S = OpVariable %_ptr_Workgroup_mat2v4float Workgroup
+       %void = OpTypeVoid
+     %uint_1 = OpConstant %uint 1
+%_arr_uint_uint_1 = OpTypeArray %uint %uint_1
+          %9 = OpTypeFunction %void %_arr_uint_uint_1
+        %int = OpTypeInt 32 1
+         %17 = OpConstantNull %int
+%_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
+         %21 = OpConstantNull %v4float
+         %22 = OpTypeFunction %void %uint
+         %26 = OpConstantNull %mat2v4float
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+         %31 = OpConstantComposite %_arr_uint_uint_1 %uint_1
+         %32 = OpTypeFunction %void
+   %func_S_X = OpFunction %void None %9
+    %pointer = OpFunctionParameter %_arr_uint_uint_1
+         %15 = OpLabel
+         %18 = OpCompositeExtract %uint %pointer 0
+         %20 = OpAccessChain %_ptr_Workgroup_v4float %S %18
+               OpStore %20 %21
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %22
+%local_invocation_index = OpFunctionParameter %uint
+         %25 = OpLabel
+               OpStore %S %26
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %30 = OpFunctionCall %void %func_S_X %31
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %32
+         %34 = OpLabel
+         %36 = OpLoad %uint %local_invocation_index_1
+         %35 = OpFunctionCall %void %main_inner %36
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.wgsl
new file mode 100644
index 0000000..b58576d
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_mat2x4.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_full_ptr_parameters;
+
+var<workgroup> S : mat2x4<f32>;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S[1]));
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl
new file mode 100644
index 0000000..f21f9bf
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+};
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) {
+  *pointer = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&S.i);
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.dxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..e9f5ab4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  float4 i;
+};
+
+groupshared str S;
+
+void func_S_i() {
+  S.i = (0.0f).xxxx;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.fxc.hlsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..e9f5ab4
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,28 @@
+struct str {
+  float4 i;
+};
+
+groupshared str S;
+
+void func_S_i() {
+  S.i = (0.0f).xxxx;
+}
+
+struct tint_symbol_1 {
+  uint local_invocation_index : SV_GroupIndex;
+};
+
+void main_inner(uint local_invocation_index) {
+  {
+    const str tint_symbol_2 = (str)0;
+    S = tint_symbol_2;
+  }
+  GroupMemoryBarrierWithGroupSync();
+  func_S_i();
+}
+
+[numthreads(1, 1, 1)]
+void main(tint_symbol_1 tint_symbol) {
+  main_inner(tint_symbol.local_invocation_index);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.glsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..4852cef
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.glsl
@@ -0,0 +1,25 @@
+#version 310 es
+
+struct str {
+  vec4 i;
+};
+
+shared str S;
+void func_S_i() {
+  S.i = vec4(0.0f);
+}
+
+void tint_symbol(uint local_invocation_index) {
+  {
+    str tint_symbol_1 = str(vec4(0.0f));
+    S = tint_symbol_1;
+  }
+  barrier();
+  func_S_i();
+}
+
+layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
+void main() {
+  tint_symbol(gl_LocalInvocationIndex);
+  return;
+}
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.msl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..d0437f0
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.msl
@@ -0,0 +1,26 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct str {
+  float4 i;
+};
+
+void func(threadgroup float4* const pointer) {
+  *(pointer) = float4(0.0f);
+}
+
+void tint_symbol_inner(uint local_invocation_index, threadgroup str* const tint_symbol_2) {
+  {
+    str const tint_symbol_1 = str{};
+    *(tint_symbol_2) = tint_symbol_1;
+  }
+  threadgroup_barrier(mem_flags::mem_threadgroup);
+  func(&((*(tint_symbol_2)).i));
+}
+
+kernel void tint_symbol(uint local_invocation_index [[thread_index_in_threadgroup]]) {
+  threadgroup str tint_symbol_3;
+  tint_symbol_inner(local_invocation_index, &(tint_symbol_3));
+  return;
+}
+
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.spvasm b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..c21aeae
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,56 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 30
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main" %local_invocation_index_1
+               OpExecutionMode %main LocalSize 1 1 1
+               OpName %local_invocation_index_1 "local_invocation_index_1"
+               OpName %str "str"
+               OpMemberName %str 0 "i"
+               OpName %S "S"
+               OpName %func_S_i "func_S_i"
+               OpName %main_inner "main_inner"
+               OpName %local_invocation_index "local_invocation_index"
+               OpName %main "main"
+               OpDecorate %local_invocation_index_1 BuiltIn LocalInvocationIndex
+               OpMemberDecorate %str 0 Offset 0
+       %uint = OpTypeInt 32 0
+%_ptr_Input_uint = OpTypePointer Input %uint
+%local_invocation_index_1 = OpVariable %_ptr_Input_uint Input
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+        %str = OpTypeStruct %v4float
+%_ptr_Workgroup_str = OpTypePointer Workgroup %str
+          %S = OpVariable %_ptr_Workgroup_str Workgroup
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void
+     %uint_0 = OpConstant %uint 0
+%_ptr_Workgroup_v4float = OpTypePointer Workgroup %v4float
+         %16 = OpConstantNull %v4float
+         %17 = OpTypeFunction %void %uint
+         %21 = OpConstantNull %str
+     %uint_2 = OpConstant %uint 2
+   %uint_264 = OpConstant %uint 264
+   %func_S_i = OpFunction %void None %9
+         %12 = OpLabel
+         %15 = OpAccessChain %_ptr_Workgroup_v4float %S %uint_0
+               OpStore %15 %16
+               OpReturn
+               OpFunctionEnd
+ %main_inner = OpFunction %void None %17
+%local_invocation_index = OpFunctionParameter %uint
+         %20 = OpLabel
+               OpStore %S %21
+               OpControlBarrier %uint_2 %uint_2 %uint_264
+         %25 = OpFunctionCall %void %func_S_i
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %9
+         %27 = OpLabel
+         %29 = OpLoad %uint %local_invocation_index_1
+         %28 = OpFunctionCall %void %main_inner %29
+               OpReturn
+               OpFunctionEnd
diff --git a/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.wgsl b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..d6841c1
--- /dev/null
+++ b/test/tint/ptr_ref/store/param/workgroup/vec4_f32_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,16 @@
+enable chromium_experimental_full_ptr_parameters;
+
+struct str {
+  i : vec4<f32>,
+}
+
+var<workgroup> S : str;
+
+fn func(pointer : ptr<workgroup, vec4<f32>>) {
+  *(pointer) = vec4<f32>();
+}
+
+@compute @workgroup_size(1)
+fn main() {
+  func(&(S.i));
+}