[tint][msl] Support pixel_local attachments

This change also removes the TINT_ENABLE_PIXEL_LOCAL_EXTENSION build flag

Change-Id: I6ab511c6bafc5b1bf60cdfba95453766159975a9
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/150146
Auto-Submit: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cb8b069..c537b05 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -187,9 +187,6 @@
 option_if_not_defined(TINT_CHECK_CHROMIUM_STYLE "Check for [chromium-style] issues during build" OFF)
 option_if_not_defined(TINT_RANDOMIZE_HASHES "Randomize the hash seed value to detect non-deterministic output" OFF)
 
-# TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
-option_if_not_defined(TINT_ENABLE_PIXEL_LOCAL_EXTENSION "Enable the WIP 'chromium_experimental_pixel_local' extension" OFF)
-
 # Recommended setting for compability with future abseil releases.
 set(ABSL_PROPAGATE_CXX_STD ON)
 
diff --git a/include/tint/tint.h b/include/tint/tint.h
index 899de9a..832bf7e 100644
--- a/include/tint/tint.h
+++ b/include/tint/tint.h
@@ -25,6 +25,7 @@
 #include "src/tint/api/options/array_length_from_uniform.h"
 #include "src/tint/api/options/binding_remapper.h"
 #include "src/tint/api/options/external_texture.h"
+#include "src/tint/api/options/pixel_local.h"
 #include "src/tint/api/options/texture_builtins_from_uniform.h"
 #include "src/tint/api/tint.h"
 #include "src/tint/lang/core/type/manager.h"
diff --git a/scripts/tint_overrides_with_defaults.gni b/scripts/tint_overrides_with_defaults.gni
index 606b530..7e2a646 100644
--- a/scripts/tint_overrides_with_defaults.gni
+++ b/scripts/tint_overrides_with_defaults.gni
@@ -86,11 +86,6 @@
   if (!defined(tint_build_unittests)) {
     tint_build_unittests = true
   }
-
-  # TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
-  if (!defined(tint_enable_pixel_local_extension)) {
-    tint_enable_pixel_local_extension = false
-  }
 }
 
 declare_args() {
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 2e9e7ae..d6570a1 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -89,13 +89,6 @@
     defines += [ "TINT_BUILD_SYNTAX_TREE_WRITER=0" ]
   }
 
-  # TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
-  if (tint_enable_pixel_local_extension) {
-    defines += [ "TINT_ENABLE_PIXEL_LOCAL_EXTENSION=1" ]
-  } else {
-    defines += [ "TINT_ENABLE_PIXEL_LOCAL_EXTENSION=0" ]
-  }
-
   include_dirs = [
     "${tint_root_dir}/",
     "${tint_root_dir}/include/",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 2632230..2da7f95 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -43,9 +43,6 @@
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_WGSL_WRITER=$<BOOL:${TINT_BUILD_WGSL_WRITER}>)
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SYNTAX_TREE_WRITER=$<BOOL:${TINT_BUILD_SYNTAX_TREE_WRITER}>)
 
-  # TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
-  target_compile_definitions(${TARGET} PUBLIC -DTINT_ENABLE_PIXEL_LOCAL_EXTENSION=$<BOOL:${TINT_ENABLE_PIXEL_LOCAL_EXTENSION}>)
-
   common_compile_options(${TARGET})
 endfunction()
 
diff --git a/src/tint/api/options/BUILD.bazel b/src/tint/api/options/BUILD.bazel
index 211f184..0e57862 100644
--- a/src/tint/api/options/BUILD.bazel
+++ b/src/tint/api/options/BUILD.bazel
@@ -32,6 +32,7 @@
     "array_length_from_uniform.h",
     "binding_remapper.h",
     "external_texture.h",
+    "pixel_local.h",
     "texture_builtins_from_uniform.h",
   ],
   deps = [
diff --git a/src/tint/api/options/BUILD.cmake b/src/tint/api/options/BUILD.cmake
index bb857ab..6f28382 100644
--- a/src/tint/api/options/BUILD.cmake
+++ b/src/tint/api/options/BUILD.cmake
@@ -30,6 +30,7 @@
   api/options/binding_remapper.h
   api/options/external_texture.h
   api/options/options.cc
+  api/options/pixel_local.h
   api/options/texture_builtins_from_uniform.h
 )
 
diff --git a/src/tint/api/options/BUILD.gn b/src/tint/api/options/BUILD.gn
index fa8c423..6b5ccb4 100644
--- a/src/tint/api/options/BUILD.gn
+++ b/src/tint/api/options/BUILD.gn
@@ -31,6 +31,7 @@
     "binding_remapper.h",
     "external_texture.h",
     "options.cc",
+    "pixel_local.h",
     "texture_builtins_from_uniform.h",
   ]
   deps = [
diff --git a/src/tint/api/options/pixel_local.h b/src/tint/api/options/pixel_local.h
new file mode 100644
index 0000000..7ab898d
--- /dev/null
+++ b/src/tint/api/options/pixel_local.h
@@ -0,0 +1,35 @@
+// Copyright 2023 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_API_OPTIONS_PIXEL_LOCAL_H_
+#define SRC_TINT_API_OPTIONS_PIXEL_LOCAL_H_
+
+#include <unordered_map>
+
+#include "src/tint/utils/reflection/reflection.h"
+
+namespace tint {
+
+/// Options used to specify pixel local mappings
+struct PixelLocalOptions {
+    /// Index of pixel_local structure member index to attachment index
+    std::unordered_map<uint32_t, uint32_t> attachments;
+
+    /// Reflect the fields of this class so that it can be used by tint::ForeachField()
+    TINT_REFLECT(attachments);
+};
+
+}  // namespace tint
+
+#endif  // SRC_TINT_API_OPTIONS_PIXEL_LOCAL_H_
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index c93b2dd..da47439 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -33,6 +33,7 @@
 #include "spirv-tools/libspirv.hpp"
 #endif  // TINT_BUILD_SPV_READER || TINT_BUILD_SPV_WRITER
 
+#include "src/tint/api/options/pixel_local.h"
 #include "src/tint/api/tint.h"
 #include "src/tint/cmd/common/generate_external_texture_bindings.h"
 #include "src/tint/cmd/common/helper.h"
@@ -166,6 +167,7 @@
     std::string xcrun_path;
     tint::Hashmap<std::string, double, 8> overrides;
     std::optional<tint::BindingPoint> hlsl_root_constant_binding_point;
+    tint::PixelLocalOptions pixel_local_options;
 
     bool dump_ir = false;
     bool use_ir = false;
@@ -358,6 +360,15 @@
 default to binding 0 of the largest used group plus 1,
 or group 0 if no resource bound)");
 
+    auto& pixel_local_attachments =
+        options.Add<StringOption>("pixel_local_attachments",
+                                  R"(Pixel local storage attachment bindings, comma-separated
+Each binding is of the form MEMBER_INDEX=ATTACHMENT_INDEX,
+where MEMBER_INDEX is the pixel-local structure member
+index and ATTACHMENT_INDEX is the index of the emitted
+attachment.
+)");
+
     auto& skip_hash = options.Add<StringOption>(
         "skip-hash", R"(Skips validation if the hash of the output is equal to any
 of the hash codes in the comma separated list of hashes)");
@@ -438,6 +449,32 @@
         opts->hlsl_root_constant_binding_point = tint::BindingPoint{group.Get(), binding.Get()};
     }
 
+    if (pixel_local_attachments.value.has_value()) {
+        auto bindings = tint::Split(*pixel_local_attachments.value, ",");
+        for (auto& binding : bindings) {
+            auto values = tint::Split(binding, "=");
+            if (values.Length() != 2) {
+                std::cerr << "Invalid binding " << pixel_local_attachments.name << ": " << binding
+                          << std::endl;
+                return false;
+            }
+            auto member_index = tint::ParseUint32(values[0]);
+            if (!member_index) {
+                std::cerr << "Invalid member index for " << pixel_local_attachments.name << ": "
+                          << values[0] << std::endl;
+                return false;
+            }
+            auto attachment_index = tint::ParseUint32(values[1]);
+            if (!attachment_index) {
+                std::cerr << "Invalid attachment index for " << pixel_local_attachments.name << ": "
+                          << values[1] << std::endl;
+                return false;
+            }
+            opts->pixel_local_options.attachments.emplace(member_index.Get(),
+                                                          attachment_index.Get());
+        }
+    }
+
     auto files = result.Get();
     if (files.Length() > 1) {
         std::cerr << "More than one input file specified: "
@@ -653,6 +690,7 @@
     gen_options.use_tint_ir = options.use_ir;
     gen_options.disable_robustness = !options.enable_robustness;
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
+    gen_options.pixel_local_options = options.pixel_local_options;
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(input_program);
     gen_options.array_length_from_uniform.ubo_binding = tint::BindingPoint{0, 30};
@@ -681,7 +719,10 @@
     auto msl_version = tint::msl::validate::MslVersion::kMsl_1_2;
     for (auto* enable : program->AST().Enables()) {
         if (enable->HasExtension(tint::core::Extension::kChromiumExperimentalSubgroups)) {
-            msl_version = tint::msl::validate::MslVersion::kMsl_2_1;
+            msl_version = std::max(msl_version, tint::msl::validate::MslVersion::kMsl_2_1);
+        }
+        if (enable->HasExtension(tint::core::Extension::kChromiumExperimentalPixelLocal)) {
+            msl_version = std::max(msl_version, tint::msl::validate::MslVersion::kMsl_2_3);
         }
     }
 
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
index 1a0a558..22fd118 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -44,6 +44,7 @@
 #include "src/tint/lang/core/type/void.h"
 #include "src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.h"
 #include "src/tint/lang/msl/writer/ast_raise/packed_vec3.h"
+#include "src/tint/lang/msl/writer/ast_raise/pixel_local.h"
 #include "src/tint/lang/msl/writer/ast_raise/subgroup_ballot.h"
 #include "src/tint/lang/msl/writer/common/printer_support.h"
 #include "src/tint/lang/wgsl/ast/alias.h"
@@ -212,6 +213,15 @@
     // SubgroupBallot() must come after CanonicalizeEntryPointIO.
     manager.Add<SubgroupBallot>();
 
+    {
+        PixelLocal::Config cfg;
+        for (auto it : options.pixel_local_options.attachments) {
+            cfg.attachments.Add(it.first, it.second);
+        }
+        data.Add<PixelLocal::Config>(cfg);
+        manager.Add<PixelLocal>();
+    }
+
     // ArrayLengthFromUniform must come after SimplifyPointers, as
     // it assumes that the form of the array length argument is &var.array.
     manager.Add<ast::transform::ArrayLengthFromUniform>();
@@ -248,14 +258,15 @@
             "MSL", builder_.AST(), diagnostics_,
             Vector{
                 core::Extension::kChromiumDisableUniformityAnalysis,
+                core::Extension::kChromiumExperimentalDp4A,
                 core::Extension::kChromiumExperimentalFullPtrParameters,
+                core::Extension::kChromiumExperimentalPixelLocal,
                 core::Extension::kChromiumExperimentalPushConstant,
                 core::Extension::kChromiumExperimentalReadWriteStorageTexture,
                 core::Extension::kChromiumExperimentalSubgroups,
+                core::Extension::kChromiumInternalDualSourceBlending,
                 core::Extension::kChromiumInternalRelaxedUniformLayout,
                 core::Extension::kF16,
-                core::Extension::kChromiumInternalDualSourceBlending,
-                core::Extension::kChromiumExperimentalDp4A,
             })) {
         return false;
     }
@@ -1987,8 +1998,20 @@
 
             bool ok = Switch(
                 type,  //
-                [&](const core::type::Struct*) {
-                    out << " [[stage_in]]";
+                [&](const core::type::Struct* str) {
+                    bool is_pixel_local = false;
+                    if (auto* sem_str = str->As<sem::Struct>()) {
+                        for (auto* member : sem_str->Members()) {
+                            if (ast::HasAttribute<PixelLocal::Attachment>(
+                                    member->Declaration()->attributes)) {
+                                is_pixel_local = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (!is_pixel_local) {
+                        out << " [[stage_in]]";
+                    }
                     return true;
                 },
                 [&](const core::type::Texture*) {
@@ -2801,7 +2824,7 @@
         if (auto location = attributes.location) {
             auto& pipeline_stage_uses = str->PipelineStageUses();
             if (TINT_UNLIKELY(pipeline_stage_uses.size() != 1)) {
-                TINT_ICE() << "invalid entry point IO struct uses";
+                TINT_ICE() << "invalid entry point IO struct uses for " << str->Name().NameView();
                 return false;
             }
 
@@ -2839,6 +2862,13 @@
             out << " " << invariant_define_name_;
         }
 
+        if (auto* sem_mem = mem->As<sem::StructMember>()) {
+            if (auto* attachment =
+                    ast::GetAttribute<PixelLocal::Attachment>(sem_mem->Declaration()->attributes)) {
+                out << " [[color(" << attachment->index << ")]]";
+            }
+        }
+
         out << ";";
 
         if (is_host_shareable) {
diff --git a/src/tint/lang/msl/writer/ast_raise/BUILD.bazel b/src/tint/lang/msl/writer/ast_raise/BUILD.bazel
index 5b004a6..0b080b5 100644
--- a/src/tint/lang/msl/writer/ast_raise/BUILD.bazel
+++ b/src/tint/lang/msl/writer/ast_raise/BUILD.bazel
@@ -28,11 +28,13 @@
   srcs = [
     "module_scope_var_to_entry_point_param.cc",
     "packed_vec3.cc",
+    "pixel_local.cc",
     "subgroup_ballot.cc",
   ],
   hdrs = [
     "module_scope_var_to_entry_point_param.h",
     "packed_vec3.h",
+    "pixel_local.h",
     "subgroup_ballot.h",
   ],
   deps = [
@@ -68,6 +70,7 @@
   srcs = [
     "module_scope_var_to_entry_point_param_test.cc",
     "packed_vec3_test.cc",
+    "pixel_local_test.cc",
     "subgroup_ballot_test.cc",
   ],
   deps = [
diff --git a/src/tint/lang/msl/writer/ast_raise/BUILD.cmake b/src/tint/lang/msl/writer/ast_raise/BUILD.cmake
index 838ec7d..56fe8bd 100644
--- a/src/tint/lang/msl/writer/ast_raise/BUILD.cmake
+++ b/src/tint/lang/msl/writer/ast_raise/BUILD.cmake
@@ -32,6 +32,8 @@
   lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.h
   lang/msl/writer/ast_raise/packed_vec3.cc
   lang/msl/writer/ast_raise/packed_vec3.h
+  lang/msl/writer/ast_raise/pixel_local.cc
+  lang/msl/writer/ast_raise/pixel_local.h
   lang/msl/writer/ast_raise/subgroup_ballot.cc
   lang/msl/writer/ast_raise/subgroup_ballot.h
 )
@@ -71,6 +73,7 @@
 tint_add_target(tint_lang_msl_writer_ast_raise_test test
   lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param_test.cc
   lang/msl/writer/ast_raise/packed_vec3_test.cc
+  lang/msl/writer/ast_raise/pixel_local_test.cc
   lang/msl/writer/ast_raise/subgroup_ballot_test.cc
 )
 
diff --git a/src/tint/lang/msl/writer/ast_raise/BUILD.gn b/src/tint/lang/msl/writer/ast_raise/BUILD.gn
index bf36d87..f6890c2 100644
--- a/src/tint/lang/msl/writer/ast_raise/BUILD.gn
+++ b/src/tint/lang/msl/writer/ast_raise/BUILD.gn
@@ -35,6 +35,8 @@
       "module_scope_var_to_entry_point_param.h",
       "packed_vec3.cc",
       "packed_vec3.h",
+      "pixel_local.cc",
+      "pixel_local.h",
       "subgroup_ballot.cc",
       "subgroup_ballot.h",
     ]
@@ -71,6 +73,7 @@
       sources = [
         "module_scope_var_to_entry_point_param_test.cc",
         "packed_vec3_test.cc",
+        "pixel_local_test.cc",
         "subgroup_ballot_test.cc",
       ]
       deps = [
diff --git a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
index 54531f7..4e529a2 100644
--- a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
+++ b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
@@ -201,6 +201,8 @@
                 }
                 break;
             }
+            case core::AddressSpace::kPixelLocal:
+                break;  // Ignore
             default: {
                 TINT_ICE() << "unhandled module-scope address space (" << sc << ")";
                 break;
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local.cc b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
new file mode 100644
index 0000000..53f6dfe
--- /dev/null
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
@@ -0,0 +1,287 @@
+// Copyright 2023 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/lang/msl/writer/ast_raise/pixel_local.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/wgsl/program/clone_context.h"
+#include "src/tint/lang/wgsl/resolver/resolve.h"
+#include "src/tint/lang/wgsl/sem/function.h"
+#include "src/tint/lang/wgsl/sem/module.h"
+#include "src/tint/lang/wgsl/sem/statement.h"
+#include "src/tint/lang/wgsl/sem/struct.h"
+#include "src/tint/utils/containers/transform.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal);
+TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal::Attachment);
+TINT_INSTANTIATE_TYPEINFO(tint::msl::writer::PixelLocal::Config);
+
+using namespace tint::core::number_suffixes;  // NOLINT
+using namespace tint::core::fluent_types;     // NOLINT
+
+namespace tint::msl::writer {
+
+/// PIMPL state for the transform
+struct PixelLocal::State {
+    /// The source program
+    const Program* const src;
+    /// The target program builder
+    ProgramBuilder b;
+    /// The clone context
+    program::CloneContext ctx = {&b, src, /* auto_clone_symbols */ true};
+    /// The transform config
+    const Config& cfg;
+
+    /// Constructor
+    /// @param program the source program
+    /// @param config the transform config
+    State(const Program* program, const Config& config) : src(program), cfg(config) {}
+
+    /// Runs the transform
+    /// @returns the new program or SkipTransform if the transform is not required
+    ApplyResult Run() {
+        auto& sem = src->Sem();
+
+        // If the pixel local extension isn't enabled, then there must be no use of pixel_local
+        // variables, and so there's nothing for this transform to do.
+        if (!sem.Module()->Extensions().Contains(
+                core::Extension::kChromiumExperimentalPixelLocal)) {
+            return SkipTransform;
+        }
+
+        bool made_changes = false;
+
+        // Change all module scope `var<pixel_local>` variables to `var<private>`.
+        // We need to do this even if the variable is not referenced by the entry point as later
+        // stages do not understand the pixel_local address space.
+        for (auto* global : src->AST().GlobalVariables()) {
+            if (auto* var = global->As<ast::Var>()) {
+                if (sem.Get(var)->AddressSpace() == core::AddressSpace::kPixelLocal) {
+                    // Change the 'var<pixel_local>' to 'var<private>'
+                    ctx.Replace(var->declared_address_space, b.Expr(core::AddressSpace::kPrivate));
+                    made_changes = true;
+                }
+            }
+        }
+
+        // Find the single entry point
+        const sem::Function* entry_point = nullptr;
+        for (auto* fn : src->AST().Functions()) {
+            if (fn->IsEntryPoint()) {
+                if (entry_point != nullptr) {
+                    TINT_ICE() << "PixelLocal transform requires that the SingleEntryPoint "
+                                  "transform has already been run";
+                    return SkipTransform;
+                }
+                entry_point = sem.Get(fn);
+
+                // Look for a `var<pixel_local>` used by the entry point...
+                for (auto* global : entry_point->TransitivelyReferencedGlobals()) {
+                    if (global->AddressSpace() != core::AddressSpace::kPixelLocal) {
+                        continue;
+                    }
+
+                    // Obtain struct of the pixel local.
+                    auto* pixel_local_str = global->Type()->UnwrapRef()->As<sem::Struct>();
+
+                    // Add an attachment decoration to each member of the pixel_local structure.
+                    for (auto* member : pixel_local_str->Members()) {
+                        ctx.InsertBack(member->Declaration()->attributes,
+                                       Attachment(AttachmentIndex(member->Index())));
+                        ctx.InsertBack(member->Declaration()->attributes,
+                                       b.Disable(ast::DisabledValidation::kEntryPointParameter));
+                    }
+
+                    TransformEntryPoint(entry_point, global, pixel_local_str);
+                    made_changes = true;
+
+                    break;  // Only a single `var<pixel_local>` can be used by an entry point.
+                }
+            }
+        }
+
+        if (!made_changes) {
+            return SkipTransform;
+        }
+
+        ctx.Clone();
+        return resolver::Resolve(b);
+    }
+
+    /// Transforms the entry point @p entry_point to handle the direct or transitive usage of the
+    /// `var<pixel_local>` @p pixel_local_var.
+    /// @param entry_point the entry point
+    /// @param pixel_local_var the `var<pixel_local>`
+    /// @param pixel_local_str the struct type of the var
+    void TransformEntryPoint(const sem::Function* entry_point,
+                             const sem::GlobalVariable* pixel_local_var,
+                             const sem::Struct* pixel_local_str) {
+        auto* fn = entry_point->Declaration();
+        auto fn_name = fn->name->symbol.Name();
+        auto pixel_local_str_name = ctx.Clone(pixel_local_str->Name());
+        auto pixel_local_var_name = ctx.Clone(pixel_local_var->Declaration()->name->symbol);
+
+        // Remove the @fragment attribute from the entry point
+        ctx.Remove(fn->attributes, ast::GetAttribute<ast::StageAttribute>(fn->attributes));
+        // Rename the entry point
+        auto inner_name = b.Symbols().New(fn_name + "_inner");
+        ctx.Replace(fn->name, b.Ident(inner_name));
+
+        // Create a new function that wraps the entry point.
+        // This function has all the existing entry point parameters and an additional
+        // parameter for the input pixel local structure.
+        auto params = ctx.Clone(fn->params);
+        auto pl_param = b.Symbols().New("pixel_local");
+        params.Push(b.Param(pl_param, b.ty(pixel_local_str_name)));
+
+        // Remove any entry-point attributes from the inner function.
+        // This must come after `ctx.Clone(fn->params)` as we want these attributes on the outer
+        // function.
+        for (auto* param : fn->params) {
+            for (auto* attr : param->attributes) {
+                if (attr->IsAnyOf<ast::BuiltinAttribute, ast::LocationAttribute,
+                                  ast::InterpolateAttribute, ast::InvariantAttribute>()) {
+                    ctx.Remove(param->attributes, attr);
+                }
+            }
+        }
+
+        // Build the outer function's statements, starting with an assignment of the pixel local
+        // parameter to the module scope var.
+        Vector<const ast::Statement*, 3> body{
+            b.Assign(pixel_local_var_name, pl_param),
+        };
+
+        // Build the arguments to call the inner function
+        auto call_args =
+            tint::Transform(fn->params, [&](auto* p) { return b.Expr(ctx.Clone(p->name)); });
+
+        // Create a structure to hold the combined flattened result of the entry point and the pixel
+        // local structure.
+        auto str_name = b.Symbols().New(fn_name + "_res");
+        Vector<const ast::StructMember*, 8> members;
+        Vector<const ast::Expression*, 8> return_args;  // arguments to the final `return` statement
+
+        auto add_member = [&](const core::type::Type* ty, VectorRef<const ast::Attribute*> attrs) {
+            members.Push(b.Member("output_" + std::to_string(members.Length()),
+                                  CreateASTTypeFor(ctx, ty), std::move(attrs)));
+        };
+        for (auto* member : pixel_local_str->Members()) {
+            add_member(member->Type(), Vector{
+                                           b.Location(AInt(AttachmentIndex(member->Index()))),
+                                       });
+            return_args.Push(b.MemberAccessor(pixel_local_var_name, ctx.Clone(member->Name())));
+        }
+        if (fn->return_type) {
+            Symbol call_result = b.Symbols().New("result");
+            if (auto* str = entry_point->ReturnType()->As<sem::Struct>()) {
+                // The entry point returned a structure.
+                for (auto* member : str->Members()) {
+                    auto& member_attrs = member->Declaration()->attributes;
+                    add_member(member->Type(), ctx.Clone(member_attrs));
+                    return_args.Push(b.MemberAccessor(call_result, ctx.Clone(member->Name())));
+                    if (auto* location = ast::GetAttribute<ast::LocationAttribute>(member_attrs)) {
+                        // Remove the @location attribute from the member of the inner function's
+                        // output structure.
+                        // Note: This will break other entry points that share the same output
+                        // structure, however this transform assumes that the SingleEntryPoint
+                        // transform will have already been run.
+                        ctx.Remove(member_attrs, location);
+                    }
+                }
+            } else {
+                // The entry point returned a non-structure
+                add_member(entry_point->ReturnType(), ctx.Clone(fn->return_type_attributes));
+                return_args.Push(b.Expr(call_result));
+
+                // Remove the @location from the inner function's return type attributes
+                ctx.Remove(fn->return_type_attributes,
+                           ast::GetAttribute<ast::LocationAttribute>(fn->return_type_attributes));
+            }
+            body.Push(b.Decl(b.Let(call_result, b.Call(inner_name, std::move(call_args)))));
+        } else {
+            body.Push(b.CallStmt(b.Call(inner_name, std::move(call_args))));
+        }
+
+        // Declare the output structure
+        b.Structure(str_name, std::move(members));
+
+        // Return the output structure
+        body.Push(b.Return(b.Call(str_name, std::move(return_args))));
+
+        // Declare the new entry point that calls the inner function
+        b.Func(fn_name, std::move(params), b.ty(str_name), body,
+               Vector{b.Stage(ast::PipelineStage::kFragment)});
+    }
+
+    /// @returns a new Attachment attribute
+    /// @param index the index of the attachment
+    PixelLocal::Attachment* Attachment(uint32_t index) {
+        return b.ASTNodes().Create<PixelLocal::Attachment>(b.ID(), b.AllocateNodeID(), index);
+    }
+
+    /// @returns the attachment index for the pixel local field with the given index
+    /// @param field_index the pixel local field index
+    uint32_t AttachmentIndex(uint32_t field_index) {
+        auto idx = cfg.attachments.Get(field_index);
+        if (TINT_UNLIKELY(!idx)) {
+            b.Diagnostics().add_error(diag::System::Transform,
+                                      "PixelLocal::Config::attachments missing entry for field " +
+                                          std::to_string(field_index));
+            return 0;
+        }
+        return *idx;
+    }
+};
+
+PixelLocal::PixelLocal() = default;
+
+PixelLocal::~PixelLocal() = default;
+
+ast::transform::Transform::ApplyResult PixelLocal::Apply(const Program* src,
+                                                         const ast::transform::DataMap& inputs,
+                                                         ast::transform::DataMap&) const {
+    auto* cfg = inputs.Get<Config>();
+    if (!cfg) {
+        ProgramBuilder b;
+        b.Diagnostics().add_error(diag::System::Transform,
+                                  "missing transform data for " + std::string(TypeInfo().name));
+        return resolver::Resolve(b);
+    }
+
+    return State(src, *cfg).Run();
+}
+
+PixelLocal::Config::Config() = default;
+
+PixelLocal::Config::Config(const Config&) = default;
+
+PixelLocal::Config::~Config() = default;
+
+PixelLocal::Attachment::Attachment(GenerationID pid, ast::NodeID nid, uint32_t idx)
+    : Base(pid, nid, Empty), index(idx) {}
+
+PixelLocal::Attachment::~Attachment() = default;
+
+std::string PixelLocal::Attachment::InternalName() const {
+    return "attachment(" + std::to_string(index) + ")";
+}
+
+const PixelLocal::Attachment* PixelLocal::Attachment::Clone(ast::CloneContext& ctx) const {
+    return ctx.dst->ASTNodes().Create<Attachment>(ctx.dst->ID(), ctx.dst->AllocateNodeID(), index);
+}
+
+}  // namespace tint::msl::writer
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local.h b/src/tint/lang/msl/writer/ast_raise/pixel_local.h
new file mode 100644
index 0000000..4d1b51f
--- /dev/null
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local.h
@@ -0,0 +1,96 @@
+// Copyright 2023 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_LANG_MSL_WRITER_AST_RAISE_PIXEL_LOCAL_H_
+#define SRC_TINT_LANG_MSL_WRITER_AST_RAISE_PIXEL_LOCAL_H_
+
+#include <string>
+
+#include "src/tint/lang/wgsl/ast/internal_attribute.h"
+#include "src/tint/lang/wgsl/ast/transform/transform.h"
+#include "src/tint/utils/containers/hashmap.h"
+
+namespace tint::msl::writer {
+
+/// PixelLocal transforms module-scope `var<pixel_local>`s and fragment entry point functions that
+/// use them:
+/// * `var<pixel_local>` will be transformed to `var<private>`.
+/// * The entry point function will be wrapped with another function ('outer') that calls the
+///  'inner' function.
+/// * The outer function will have an additional parameter of the pixel local struct type, which is
+///   copied to the module-scope var before calling the 'inner' function.
+/// * The outer function will have a new struct return type which holds both the pixel local members
+///   and the returned value(s) of the 'inner' function.
+/// @note PixelLocal requires that the SingleEntryPoint transform has already been run
+class PixelLocal final : public Castable<PixelLocal, ast::transform::Transform> {
+  public:
+    /// Transform configuration options
+    struct Config final : public Castable<Config, ast::transform::Data> {
+        /// Constructor
+        Config();
+
+        /// Copy Constructor
+        Config(const Config&);
+
+        /// Destructor
+        ~Config() override;
+
+        /// Index of pixel_local structure member index to attachment index
+        Hashmap<uint32_t, uint32_t, 8> attachments;
+    };
+
+    /// Intrinsic is an InternalAttribute that's used to decorate a pixel local attachment
+    /// parameter, return value or structure member.
+    class Attachment final : public Castable<Attachment, ast::InternalAttribute> {
+      public:
+        /// Constructor
+        /// @param pid the identifier of the program that owns this node
+        /// @param nid the unique node identifier
+        /// @param idx the attachment index
+        Attachment(GenerationID pid, ast::NodeID nid, uint32_t idx);
+
+        /// Destructor
+        ~Attachment() override;
+
+        /// @return a short description of the internal attribute which will be
+        /// displayed as `@internal(<name>)`
+        std::string InternalName() const override;
+
+        /// Performs a deep clone of this object using the program::CloneContext `ctx`.
+        /// @param ctx the clone context
+        /// @return the newly cloned object
+        const Attachment* Clone(ast::CloneContext& ctx) const override;
+
+        /// The attachment index
+        const uint32_t index;
+    };
+
+    /// Constructor
+    PixelLocal();
+
+    /// Destructor
+    ~PixelLocal() override;
+
+    /// @copydoc ast::transform::Transform::Apply
+    ApplyResult Apply(const Program* program,
+                      const ast::transform::DataMap& inputs,
+                      ast::transform::DataMap& outputs) const override;
+
+  private:
+    struct State;
+};
+
+}  // namespace tint::msl::writer
+
+#endif  // SRC_TINT_LANG_MSL_WRITER_AST_RAISE_PIXEL_LOCAL_H_
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local_test.cc b/src/tint/lang/msl/writer/ast_raise/pixel_local_test.cc
new file mode 100644
index 0000000..4b634f9
--- /dev/null
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local_test.cc
@@ -0,0 +1,799 @@
+// Copyright 2023 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/lang/msl/writer/ast_raise/pixel_local.h"
+
+#include <utility>
+
+#include "src/tint/lang/wgsl/ast/transform/helper_test.h"
+
+namespace tint::msl::writer {
+namespace {
+
+struct Binding {
+    uint32_t field_index;
+    uint32_t attachment_index;
+};
+
+ast::transform::DataMap Bindings(std::initializer_list<Binding> bindings) {
+    PixelLocal::Config cfg;
+    for (auto& binding : bindings) {
+        cfg.attachments.Add(binding.field_index, binding.attachment_index);
+    }
+    ast::transform::DataMap data;
+    data.Add<PixelLocal::Config>(std::move(cfg));
+    return data;
+}
+
+using PixelLocalTest = ast::transform::TransformTest;
+
+TEST_F(PixelLocalTest, EmptyModule) {
+    auto* src = "";
+
+    EXPECT_FALSE(ShouldRun<PixelLocal>(src, Bindings({})));
+}
+
+TEST_F(PixelLocalTest, Var) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : i32,
+};
+
+var<pixel_local> P : PixelLocal;
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : i32,
+}
+
+var<private> P : PixelLocal;
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, UseInEntryPoint) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn F() {
+  P.a += 42;
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner();
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+fn F_inner() {
+  P.a += 42;
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, UseInCallee) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn X() {
+  P.a += 42;
+}
+
+@fragment
+fn F() {
+  X();
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner();
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+fn X() {
+  P.a += 42;
+}
+
+fn F_inner() {
+  X();
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, MultipleAttachments) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn F() {
+  P.a = 42;
+  P.b = i32(P.c);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+  @location(0)
+  output_1 : i32,
+  @location(10)
+  output_2 : f32,
+}
+
+@fragment
+fn F(pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner();
+  return F_res(P.a, P.b, P.c);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+  @internal(attachment(0)) @internal(disable_validation__entry_point_parameter)
+  b : i32,
+  @internal(attachment(10)) @internal(disable_validation__entry_point_parameter)
+  c : f32,
+}
+
+var<private> P : PixelLocal;
+
+fn F_inner() {
+  P.a = 42;
+  P.b = i32(P.c);
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}, {1, 0}, {2, 10}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithBuiltinInputParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn F(@builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(@builtin(position) pos : vec4f, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(pos);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+fn F_inner(pos : vec4f) {
+  P.a += u32(pos.x);
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithInvariantBuiltinInputParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn F(@invariant @builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(@invariant @builtin(position) pos : vec4f, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(pos);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+fn F_inner(pos : vec4f) {
+  P.a += u32(pos.x);
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithBuiltinInputStructParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position) pos : vec4f,
+}
+
+@fragment
+fn F(in : In) {
+  P.a += u32(in.pos.x);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(in : In, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(in);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+fn F_inner(in : In) {
+  P.a += u32(in.pos.x);
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithInvariantBuiltinInputStructParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position) pos : vec4f,
+}
+
+@fragment
+fn F(in : In) {
+  P.a += u32(in.pos.x);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(in : In, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(in);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position)
+  pos : vec4f,
+}
+
+fn F_inner(in : In) {
+  P.a += u32(in.pos.x);
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithLocationInputParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn F(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f) {
+  P.a += u32(a.x) + u32(b.y);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(a, b);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+fn F_inner(a : vec4f, b : vec4f) {
+  P.a += (u32(a.x) + u32(b.y));
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithLocationInputStructParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0) a : vec4f,
+  @interpolate(flat) @location(1) b : vec4f,
+}
+
+@fragment
+fn F(in : In) {
+  P.a += u32(in.a.x) + u32(in.b.y);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(in : In, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(in);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+struct In {
+  @location(0)
+  a : vec4f,
+  @interpolate(flat) @location(1)
+  b : vec4f,
+}
+
+fn F_inner(in : In) {
+  P.a += (u32(in.a.x) + u32(in.b.y));
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithBuiltinAndLocationInputParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn F(@builtin(position) pos : vec4f, @location(0) uv : vec4f) {
+  P.a += u32(pos.x) + u32(uv.x);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(@builtin(position) pos : vec4f, @location(0) uv : vec4f, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(pos, uv);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+fn F_inner(pos : vec4f, uv : vec4f) {
+  P.a += (u32(pos.x) + u32(uv.x));
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithBuiltinAndLocationInputStructParameter) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position) pos : vec4f,
+  @location(0) uv : vec4f,
+}
+
+@fragment
+fn F(in : In) {
+  P.a += u32(in.pos.x) + u32(in.uv.x);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+}
+
+@fragment
+fn F(in : In, pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  F_inner(in);
+  return F_res(P.a);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+  @location(0)
+  uv : vec4f,
+}
+
+fn F_inner(in : In) {
+  P.a += (u32(in.pos.x) + u32(in.uv.x));
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithSingleFragmentOutput) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn F() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(1);
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+  @location(0)
+  output_1 : vec4<f32>,
+}
+
+@fragment
+fn F(pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  let result = F_inner();
+  return F_res(P.a, result);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+}
+
+var<private> P : PixelLocal;
+
+fn F_inner() -> vec4f {
+  P.a += 42;
+  return vec4f(1);
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(PixelLocalTest, WithMultipleFragmentOutputs) {
+    auto* src = R"(
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Output {
+  @location(0) x : vec4f,
+  @location(2) y : vec4f,
+}
+
+@fragment
+fn F() -> Output {
+  P.a += 42;
+  return Output(vec4f(1), vec4f(9));
+}
+)";
+
+    auto* expect =
+        R"(
+enable chromium_experimental_pixel_local;
+
+struct F_res {
+  @location(1)
+  output_0 : u32,
+  @location(5)
+  output_1 : u32,
+  @location(0)
+  output_2 : vec4<f32>,
+  @location(2)
+  output_3 : vec4<f32>,
+}
+
+@fragment
+fn F(pixel_local_1 : PixelLocal) -> F_res {
+  P = pixel_local_1;
+  let result = F_inner();
+  return F_res(P.a, P.b, result.x, result.y);
+}
+
+struct PixelLocal {
+  @internal(attachment(1)) @internal(disable_validation__entry_point_parameter)
+  a : u32,
+  @internal(attachment(5)) @internal(disable_validation__entry_point_parameter)
+  b : u32,
+}
+
+var<private> P : PixelLocal;
+
+struct Output {
+  x : vec4f,
+  y : vec4f,
+}
+
+fn F_inner() -> Output {
+  P.a += 42;
+  return Output(vec4f(1), vec4f(9));
+}
+)";
+
+    auto got = Run<PixelLocal>(src, Bindings({{0, 1}, {1, 5}}));
+
+    EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace tint::msl::writer
diff --git a/src/tint/lang/msl/writer/common/options.h b/src/tint/lang/msl/writer/common/options.h
index 0b23a08..763b1d3 100644
--- a/src/tint/lang/msl/writer/common/options.h
+++ b/src/tint/lang/msl/writer/common/options.h
@@ -18,6 +18,7 @@
 #include "src/tint/api/options/array_length_from_uniform.h"
 #include "src/tint/api/options/binding_remapper.h"
 #include "src/tint/api/options/external_texture.h"
+#include "src/tint/api/options/pixel_local.h"
 #include "src/tint/utils/reflection/reflection.h"
 
 namespace tint::msl::writer {
@@ -52,6 +53,9 @@
     /// Set to `true` to disable workgroup memory zero initialization
     bool disable_workgroup_init = false;
 
+    /// Options used for dealing with pixel local storage
+    PixelLocalOptions pixel_local_options = {};
+
     /// Options used in the binding mappings for external textures
     ExternalTextureOptions external_texture_options = {};
 
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
index be33712..e9d244e 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
@@ -176,16 +176,16 @@
         }
     }
 
-    /// Clones the shader IO attributes from @p in.
+    /// Clones the shader IO and internal attributes from @p in.
     /// @param in the attributes to clone
     /// @param do_interpolate whether to clone InterpolateAttribute
     /// @return the cloned attributes
-    template <size_t N>
-    auto CloneShaderIOAttributes(const tint::Vector<const Attribute*, N> in, bool do_interpolate) {
-        tint::Vector<const Attribute*, N> out;
+    auto CloneShaderIOAttributes(tint::VectorRef<const Attribute*> in, bool do_interpolate) {
+        tint::Vector<const Attribute*, 8> out;
         for (auto* attr : in) {
-            if (IsShaderIOAttribute(attr) &&
-                (do_interpolate || !attr->template Is<InterpolateAttribute>())) {
+            if ((IsShaderIOAttribute(attr) &&
+                 (do_interpolate || !attr->template Is<InterpolateAttribute>())) ||
+                attr->Is<ast::InternalAttribute>()) {
                 CloneAttribute(attr, out);
             }
         }
diff --git a/src/tint/lang/wgsl/resolver/function_validation_test.cc b/src/tint/lang/wgsl/resolver/function_validation_test.cc
index 5119f6e..e1d479a 100644
--- a/src/tint/lang/wgsl/resolver/function_validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/function_validation_test.cc
@@ -1054,13 +1054,7 @@
     Func("f", Vector{arg}, ty.void_(), tint::Empty);
 
     if (param.address_space == core::AddressSpace::kPixelLocal) {
-#if !TINT_ENABLE_PIXEL_LOCAL_EXTENSION
-        // TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is
-        // production-ready
-        GTEST_SKIP() << "requires TINT_ENABLE_PIXEL_LOCAL_EXTENSION";
-#else
         Enable(core::Extension::kChromiumExperimentalPixelLocal);
-#endif
     }
 
     if (param.expectation == Expectation::kAlwaysPass) {
@@ -1090,13 +1084,7 @@
     Func("f", Vector{arg}, ty.void_(), tint::Empty);
 
     if (param.address_space == core::AddressSpace::kPixelLocal) {
-#if !TINT_ENABLE_PIXEL_LOCAL_EXTENSION
-        // TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is
-        // production-ready
-        GTEST_SKIP() << "requires TINT_ENABLE_PIXEL_LOCAL_EXTENSION";
-#else
         Enable(core::Extension::kChromiumExperimentalPixelLocal);
-#endif
     }
 
     if (param.expectation == Expectation::kAlwaysPass ||
diff --git a/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc b/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc
index 31684c7..2c97b5d 100644
--- a/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc
+++ b/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc
@@ -35,15 +35,7 @@
 
     GlobalVar("v", ty("S"), core::AddressSpace::kPixelLocal);
 
-#if TINT_ENABLE_PIXEL_LOCAL_EXTENSION
     EXPECT_TRUE(r()->Resolve()) << r()->error();
-#else
-    // TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
-    EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(
-        r()->error(),
-        R"(12:34 error: chromium_experimental_pixel_local requires TINT_ENABLE_PIXEL_LOCAL_EXTENSION)");
-#endif
 }
 
 TEST_F(ResolverPixelLocalExtensionTest, AddressSpaceUsedWithoutExtension) {
@@ -66,8 +58,6 @@
         R"(12:34 error: 'pixel_local' address space requires the 'chromium_experimental_pixel_local' extension enabled)");
 }
 
-#if TINT_ENABLE_PIXEL_LOCAL_EXTENSION
-
 TEST_F(ResolverPixelLocalExtensionTest, PixelLocalTwoVariablesUsedInEntryPoint) {
     // enable chromium_experimental_pixel_local;
     // struct S { i : i32 }
@@ -357,7 +347,5 @@
 
 }  // namespace type_tests
 
-#endif
-
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index 5ac5d21..5327b0d 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -3998,16 +3998,6 @@
     for (auto* ext : enable->extensions) {
         Mark(ext);
         enabled_extensions_.Add(ext->name);
-
-// TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
-#if !TINT_ENABLE_PIXEL_LOCAL_EXTENSION
-        if (ext->name == core::Extension::kChromiumExperimentalPixelLocal) {
-            AddError(std::string(core::ToString(core::Extension::kChromiumExperimentalPixelLocal)) +
-                         " requires TINT_ENABLE_PIXEL_LOCAL_EXTENSION",
-                     enable->source);
-            return false;
-        }
-#endif
     }
     return true;
 }
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl
new file mode 100644
index 0000000..f9fbbad
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl
@@ -0,0 +1,14 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f(@builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..63afc81
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.dxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..63afc81
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.fxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.glsl
new file mode 100644
index 0000000..2b71f5c
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.glsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.msl
new file mode 100644
index 0000000..50d6a5b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.msl
@@ -0,0 +1,38 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(float4 pos, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + tint_ftou(pos[0]));
+}
+
+void f_inner_1(float4 pos, thread tint_private_vars_struct* const tint_private_vars) {
+  f_inner(pos, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.spvasm
new file mode 100644
index 0000000..0d6260a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.wgsl
new file mode 100644
index 0000000..13cc93c
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin.wgsl.expected.wgsl
@@ -0,0 +1,14 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl
new file mode 100644
index 0000000..d675132
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl
@@ -0,0 +1,14 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f(@builtin(position) pos : vec4f, @location(0) uv : vec4f) {
+  P.a += u32(pos.x) + u32(uv.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..5bafbcc
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.dxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f, @location(0) uv : vec4f) {
+  P.a += (u32(pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..5bafbcc
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.fxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f, @location(0) uv : vec4f) {
+  P.a += (u32(pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.glsl
new file mode 100644
index 0000000..a39202c
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.glsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f, @location(0) uv : vec4f) {
+  P.a += (u32(pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.msl
new file mode 100644
index 0000000..45aa35f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.msl
@@ -0,0 +1,42 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct tint_symbol_1 {
+  float4 uv [[user(locn0)]];
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(float4 pos, float4 uv, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + (tint_ftou(pos[0]) + tint_ftou(uv[0])));
+}
+
+void f_inner_1(float4 pos, tint_symbol_1 tint_symbol, thread tint_private_vars_struct* const tint_private_vars) {
+  f_inner(pos, tint_symbol.uv, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], tint_symbol_1 tint_symbol [[stage_in]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, tint_symbol, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.spvasm
new file mode 100644
index 0000000..0c7901e
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f, @location(0) uv : vec4f) {
+  P.a += (u32(pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.wgsl
new file mode 100644
index 0000000..98e107a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location.wgsl.expected.wgsl
@@ -0,0 +1,14 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@builtin(position) pos : vec4f, @location(0) uv : vec4f) {
+  P.a += (u32(pos.x) + u32(uv.x));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl
new file mode 100644
index 0000000..a3ecc54
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl
@@ -0,0 +1,18 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0) uv : vec4f,
+}
+
+@fragment fn f(@builtin(position) pos : vec4f, in : In) {
+  P.a += u32(pos.x) + u32(in.uv.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..f4c0ede
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(@builtin(position) pos : vec4f, tint_symbol : In) {
+  P.a += (u32(pos.x) + u32(tint_symbol.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..f4c0ede
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(@builtin(position) pos : vec4f, tint_symbol : In) {
+  P.a += (u32(pos.x) + u32(tint_symbol.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..4c18e73
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.glsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(@builtin(position) pos : vec4f, tint_symbol : In) {
+  P.a += (u32(pos.x) + u32(tint_symbol.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..1273933
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.msl
@@ -0,0 +1,47 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct tint_symbol_1 {
+  float4 uv [[user(locn0)]];
+};
+
+struct In {
+  float4 uv;
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(float4 pos, In in, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + (tint_ftou(pos[0]) + tint_ftou(in.uv[0])));
+}
+
+void f_inner_1(float4 pos, tint_symbol_1 tint_symbol, thread tint_private_vars_struct* const tint_private_vars) {
+  In const tint_symbol_2 = {.uv=tint_symbol.uv};
+  f_inner(pos, tint_symbol_2, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], tint_symbol_1 tint_symbol [[stage_in]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, tint_symbol, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..88fb4a1
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(@builtin(position) pos : vec4f, in : In) {
+  P.a += (u32(pos.x) + u32(in.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..c796f85
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_and_location_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,19 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(@builtin(position) pos : vec4f, in : In) {
+  P.a += (u32(pos.x) + u32(in.uv.x));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl
new file mode 100644
index 0000000..89c5296
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl
@@ -0,0 +1,18 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position) pos : vec4f,
+}
+
+@fragment fn f(in : In) {
+  P.a += u32(in.pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..3193793
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += u32(tint_symbol.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..3193793
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += u32(tint_symbol.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..215cab1
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.glsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += u32(tint_symbol.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..c34efb8
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.msl
@@ -0,0 +1,43 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct In {
+  float4 pos;
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(In in, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + tint_ftou(in.pos[0]));
+}
+
+void f_inner_1(float4 pos, thread tint_private_vars_struct* const tint_private_vars) {
+  In const tint_symbol = {.pos=pos};
+  f_inner(tint_symbol, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..14dba3f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += u32(in.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..95b10b5
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,19 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += u32(in.pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl
new file mode 100644
index 0000000..bbd0a13
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl
@@ -0,0 +1,18 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position) pos : vec4f,
+}
+
+@fragment fn f(in : In, @location(0) uv : vec4f) {
+  P.a += u32(in.pos.x) + u32(uv.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..527af3f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.dxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In, @location(0) uv : vec4f) {
+  P.a += (u32(tint_symbol.pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..527af3f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.fxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In, @location(0) uv : vec4f) {
+  P.a += (u32(tint_symbol.pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.glsl
new file mode 100644
index 0000000..101433e
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.glsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In, @location(0) uv : vec4f) {
+  P.a += (u32(tint_symbol.pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.msl
new file mode 100644
index 0000000..36509ab
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.msl
@@ -0,0 +1,47 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct tint_symbol_1 {
+  float4 uv [[user(locn0)]];
+};
+
+struct In {
+  float4 pos;
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(In in, float4 uv, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + (tint_ftou(in.pos[0]) + tint_ftou(uv[0])));
+}
+
+void f_inner_1(float4 pos, tint_symbol_1 tint_symbol, thread tint_private_vars_struct* const tint_private_vars) {
+  In const tint_symbol_2 = {.pos=pos};
+  f_inner(tint_symbol_2, tint_symbol.uv, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], tint_symbol_1 tint_symbol [[stage_in]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, tint_symbol, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.spvasm
new file mode 100644
index 0000000..0874362
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.spvasm
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(in : In, @location(0) uv : vec4f) {
+  P.a += (u32(in.pos.x) + u32(uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.wgsl
new file mode 100644
index 0000000..a4dbf65
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location.wgsl.expected.wgsl
@@ -0,0 +1,19 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(in : In, @location(0) uv : vec4f) {
+  P.a += (u32(in.pos.x) + u32(uv.x));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl
new file mode 100644
index 0000000..b9b9eb9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl
@@ -0,0 +1,19 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position) pos : vec4f,
+  @location(0) uv : vec4f,
+}
+
+@fragment fn f(in : In) {
+  P.a += u32(in.pos.x) + u32(in.uv.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..2f42cc9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += (u32(tint_symbol.pos.x) + u32(tint_symbol.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..2f42cc9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += (u32(tint_symbol.pos.x) + u32(tint_symbol.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..7a123de
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.glsl
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += (u32(tint_symbol.pos.x) + u32(tint_symbol.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..bfabcb6
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.msl
@@ -0,0 +1,48 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct tint_symbol_1 {
+  float4 uv [[user(locn0)]];
+};
+
+struct In {
+  float4 pos;
+  float4 uv;
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(In in, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + (tint_ftou(in.pos[0]) + tint_ftou(in.uv[0])));
+}
+
+void f_inner_1(float4 pos, tint_symbol_1 tint_symbol, thread tint_private_vars_struct* const tint_private_vars) {
+  In const tint_symbol_2 = {.pos=pos, .uv=tint_symbol.uv};
+  f_inner(tint_symbol_2, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], tint_symbol_1 tint_symbol [[stage_in]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, tint_symbol, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..6758d7c
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += (u32(in.pos.x) + u32(in.uv.x));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..7d2ec06
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/builtin_in_struct_and_location_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,21 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @builtin(position)
+  pos : vec4f,
+  @location(0)
+  uv : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += (u32(in.pos.x) + u32(in.uv.x));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl
new file mode 100644
index 0000000..02916e3
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl
@@ -0,0 +1,14 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f(@invariant @builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..98e76da
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.dxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@invariant @builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..98e76da
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.fxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@invariant @builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.glsl
new file mode 100644
index 0000000..7c0b2bb
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.glsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@invariant @builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.msl
new file mode 100644
index 0000000..50d6a5b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.msl
@@ -0,0 +1,38 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(float4 pos, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + tint_ftou(pos[0]));
+}
+
+void f_inner_1(float4 pos, thread tint_private_vars_struct* const tint_private_vars) {
+  f_inner(pos, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.spvasm
new file mode 100644
index 0000000..adce8e5
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@invariant @builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.wgsl
new file mode 100644
index 0000000..a32b186
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin.wgsl.expected.wgsl
@@ -0,0 +1,14 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@invariant @builtin(position) pos : vec4f) {
+  P.a += u32(pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl
new file mode 100644
index 0000000..6c31d00
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl
@@ -0,0 +1,18 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position) pos : vec4f,
+}
+
+@fragment fn f(in : In) {
+  P.a += u32(in.pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..db03744
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += u32(tint_symbol.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..db03744
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += u32(tint_symbol.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..dcb5826
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.glsl
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += u32(tint_symbol.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..c34efb8
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.msl
@@ -0,0 +1,43 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct In {
+  float4 pos;
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(In in, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + tint_ftou(in.pos[0]));
+}
+
+void f_inner_1(float4 pos, thread tint_private_vars_struct* const tint_private_vars) {
+  In const tint_symbol = {.pos=pos};
+  f_inner(tint_symbol, tint_private_vars);
+}
+
+fragment f_res f(float4 pos [[position]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(pos, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..acf24af
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,27 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += u32(in.pos.x);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..07449a8
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/invariant_builtin_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,19 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @invariant @builtin(position)
+  pos : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += u32(in.pos.x);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl
new file mode 100644
index 0000000..0273ad9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl
@@ -0,0 +1,14 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f) {
+  P.a += u32(a.x) + u32(b.y);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..228feef
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.dxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f) {
+  P.a += (u32(a.x) + u32(b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..228feef
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.fxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f) {
+  P.a += (u32(a.x) + u32(b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.glsl
new file mode 100644
index 0000000..1b82ec6
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.glsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f) {
+  P.a += (u32(a.x) + u32(b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.msl
new file mode 100644
index 0000000..8061bf4
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.msl
@@ -0,0 +1,43 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct tint_symbol_1 {
+  float4 a [[user(locn0)]];
+  float4 b [[user(locn1)]] [[flat]];
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(float4 a, float4 b, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + (tint_ftou(a[0]) + tint_ftou(b[1])));
+}
+
+void f_inner_1(tint_symbol_1 tint_symbol, thread tint_private_vars_struct* const tint_private_vars) {
+  f_inner(tint_symbol.a, tint_symbol.b, tint_private_vars);
+}
+
+fragment f_res f(tint_symbol_1 tint_symbol [[stage_in]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(tint_symbol, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.spvasm
new file mode 100644
index 0000000..dde84f8
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f) {
+  P.a += (u32(a.x) + u32(b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.wgsl
new file mode 100644
index 0000000..9fe91df
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location.wgsl.expected.wgsl
@@ -0,0 +1,14 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f(@location(0) a : vec4f, @interpolate(flat) @location(1) b : vec4f) {
+  P.a += (u32(a.x) + u32(b.y));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl
new file mode 100644
index 0000000..b9666cc
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl
@@ -0,0 +1,19 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0) a : vec4f,
+  @interpolate(flat) @location(1) b : vec4f,
+}
+
+@fragment fn f(in : In) {
+  P.a += u32(in.a.x) + u32(in.b.y);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..6b2953b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.dxc.hlsl
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  a : vec4f,
+  @interpolate(flat) @location(1)
+  b : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += (u32(tint_symbol.a.x) + u32(tint_symbol.b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..6b2953b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.fxc.hlsl
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  a : vec4f,
+  @interpolate(flat) @location(1)
+  b : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += (u32(tint_symbol.a.x) + u32(tint_symbol.b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.glsl
new file mode 100644
index 0000000..c370c80
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.glsl
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  a : vec4f,
+  @interpolate(flat) @location(1)
+  b : vec4f,
+}
+
+@fragment
+fn f(tint_symbol : In) {
+  P.a += (u32(tint_symbol.a.x) + u32(tint_symbol.b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.msl
new file mode 100644
index 0000000..faec0aa
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.msl
@@ -0,0 +1,49 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+struct tint_symbol_1 {
+  float4 a [[user(locn0)]];
+  float4 b [[user(locn1)]] [[flat]];
+};
+
+struct In {
+  float4 a;
+  float4 b;
+};
+
+uint tint_ftou(float v) {
+  return select(4294967295u, select(uint(v), 0u, (v < 0.0f)), (v < 4294967040.0f));
+}
+
+void f_inner(In in, thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + (tint_ftou(in.a[0]) + tint_ftou(in.b[1])));
+}
+
+void f_inner_1(tint_symbol_1 tint_symbol, thread tint_private_vars_struct* const tint_private_vars) {
+  In const tint_symbol_2 = {.a=tint_symbol.a, .b=tint_symbol.b};
+  f_inner(tint_symbol_2, tint_private_vars);
+}
+
+fragment f_res f(tint_symbol_1 tint_symbol [[stage_in]], PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner_1(tint_symbol, &(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.spvasm
new file mode 100644
index 0000000..70f7310
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.spvasm
@@ -0,0 +1,29 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  a : vec4f,
+  @interpolate(flat) @location(1)
+  b : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += (u32(in.a.x) + u32(in.b.y));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.wgsl
new file mode 100644
index 0000000..3d16f93
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/additional_params/location_in_struct.wgsl.expected.wgsl
@@ -0,0 +1,21 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct In {
+  @location(0)
+  a : vec4f,
+  @interpolate(flat) @location(1)
+  b : vec4f,
+}
+
+@fragment
+fn f(in : In) {
+  P.a += (u32(in.a.x) + u32(in.b.y));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl
new file mode 100644
index 0000000..e106db5
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl
@@ -0,0 +1,21 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0) x : vec4f,
+  @location(2) y : vec4f,
+  @location(4) z : vec4f,
+}
+
+@fragment fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..5fb3ea9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
@@ -0,0 +1,32 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..5fb3ea9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
@@ -0,0 +1,32 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.glsl
new file mode 100644
index 0000000..c9275ef
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.glsl
@@ -0,0 +1,32 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.msl
new file mode 100644
index 0000000..fb1717d
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.msl
@@ -0,0 +1,56 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+  float4 output_3 [[color(0)]];
+  float4 output_4 [[color(2)]];
+  float4 output_5 [[color(4)]];
+};
+
+struct tint_symbol {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+struct Out {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+Out f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 42u);
+  Out const tint_symbol_1 = Out{.x=float4(10.0f), .y=float4(20.0f), .z=float4(30.0f)};
+  return tint_symbol_1;
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  Out const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.x = inner_result.x;
+  wrapper_result.y = inner_result.y;
+  wrapper_result.z = inner_result.z;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c, .output_3=result.x, .output_4=result.y, .output_5=result.z};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.spvasm
new file mode 100644
index 0000000..9577480
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.spvasm
@@ -0,0 +1,32 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.wgsl
new file mode 100644
index 0000000..a63e4b9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/multiple_attachments.wgsl.expected.wgsl
@@ -0,0 +1,24 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl
new file mode 100644
index 0000000..393b271
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl
@@ -0,0 +1,19 @@
+// flags: --pixel_local_attachments 0=1
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0) x : vec4f,
+  @location(2) y : vec4f,
+  @location(3) z : vec4f,
+}
+
+@fragment fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..21598b3
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.dxc.hlsl
@@ -0,0 +1,30 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..21598b3
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.fxc.hlsl
@@ -0,0 +1,30 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.glsl
new file mode 100644
index 0000000..817125f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.glsl
@@ -0,0 +1,30 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.msl
new file mode 100644
index 0000000..5fc419e
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.msl
@@ -0,0 +1,52 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  float4 output_1 [[color(0)]];
+  float4 output_2 [[color(2)]];
+  float4 output_3 [[color(3)]];
+};
+
+struct tint_symbol {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+struct Out {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+Out f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 42u);
+  Out const tint_symbol_1 = Out{.x=float4(10.0f), .y=float4(20.0f), .z=float4(30.0f)};
+  return tint_symbol_1;
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  Out const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.x = inner_result.x;
+  wrapper_result.y = inner_result.y;
+  wrapper_result.z = inner_result.z;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=result.x, .output_2=result.y, .output_3=result.z};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.spvasm
new file mode 100644
index 0000000..c8a36a3
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.spvasm
@@ -0,0 +1,30 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.wgsl
new file mode 100644
index 0000000..096501a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/multiple_outputs/single_attachment.wgsl.expected.wgsl
@@ -0,0 +1,22 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+@fragment
+fn f() -> Out {
+  P.a += 42;
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl
new file mode 100644
index 0000000..62c1c27
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl
@@ -0,0 +1,15 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..45e9e60
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.dxc.hlsl
@@ -0,0 +1,23 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..45e9e60
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.fxc.hlsl
@@ -0,0 +1,23 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.glsl
new file mode 100644
index 0000000..865897f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.glsl
@@ -0,0 +1,23 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.msl
new file mode 100644
index 0000000..c8a44f9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.msl
@@ -0,0 +1,43 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+  float4 output_3 [[color(0)]];
+};
+
+struct tint_symbol {
+  float4 value;
+};
+
+float4 f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 42u);
+  return float4(2.0f);
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  float4 const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c, .output_3=result.value};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.spvasm
new file mode 100644
index 0000000..5888403
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.spvasm
@@ -0,0 +1,23 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.wgsl
new file mode 100644
index 0000000..9c9f9f6
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/multiple_attachments.wgsl.expected.wgsl
@@ -0,0 +1,15 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl
new file mode 100644
index 0000000..4bbbaaa
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl
@@ -0,0 +1,13 @@
+// flags: --pixel_local_attachments 0=1
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8d4e6f1
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.dxc.hlsl
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8d4e6f1
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.fxc.hlsl
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.glsl
new file mode 100644
index 0000000..89096e8
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.msl
new file mode 100644
index 0000000..1300a70
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.msl
@@ -0,0 +1,39 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  float4 output_1 [[color(0)]];
+};
+
+struct tint_symbol {
+  float4 value;
+};
+
+float4 f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 42u);
+  return float4(2.0f);
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  float4 const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=result.value};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.spvasm
new file mode 100644
index 0000000..abc1e39
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.spvasm
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.wgsl
new file mode 100644
index 0000000..137ee5a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/one_output/single_attachment.wgsl.expected.wgsl
@@ -0,0 +1,13 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() -> @location(0) vec4f {
+  P.a += 42;
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl
new file mode 100644
index 0000000..988b86f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl
@@ -0,0 +1,14 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f() {
+  P.a += 42;
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..46fa77f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..46fa77f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.glsl
new file mode 100644
index 0000000..1d5f098
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.glsl
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.msl
new file mode 100644
index 0000000..b12ffe8
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.msl
@@ -0,0 +1,30 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+void f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 42u);
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.spvasm
new file mode 100644
index 0000000..132062d
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.spvasm
@@ -0,0 +1,22 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.wgsl
new file mode 100644
index 0000000..c424b4b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/multiple_attachments.wgsl.expected.wgsl
@@ -0,0 +1,14 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl
new file mode 100644
index 0000000..b704ddb
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl
@@ -0,0 +1,12 @@
+// flags: --pixel_local_attachments 0=1
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment fn f() {
+  P.a += 42;
+}
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..26fc6e7
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.dxc.hlsl
@@ -0,0 +1,20 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..26fc6e7
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.fxc.hlsl
@@ -0,0 +1,20 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.glsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.glsl
new file mode 100644
index 0000000..a6316f2
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.glsl
@@ -0,0 +1,20 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.msl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.msl
new file mode 100644
index 0000000..f86668b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.msl
@@ -0,0 +1,26 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+};
+
+void f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 42u);
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a};
+}
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.spvasm
new file mode 100644
index 0000000..6c4082b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.spvasm
@@ -0,0 +1,20 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
+
+Failed to generate: extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.wgsl
new file mode 100644
index 0000000..56a1697
--- /dev/null
+++ b/test/tint/extensions/pixel_local/entry_point_use/zero_outputs/single_attachment.wgsl.expected.wgsl
@@ -0,0 +1,12 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+@fragment
+fn f() {
+  P.a += 42;
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl
new file mode 100644
index 0000000..6087c37
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl
@@ -0,0 +1,35 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0) x : vec4f,
+  @location(2) y : vec4f,
+  @location(4) z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..8d3d555
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
@@ -0,0 +1,46 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..8d3d555
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
@@ -0,0 +1,46 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.glsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.glsl
new file mode 100644
index 0000000..ca1b6d7
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.glsl
@@ -0,0 +1,46 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.msl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.msl
new file mode 100644
index 0000000..30fd975
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.msl
@@ -0,0 +1,70 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+  float4 output_3 [[color(0)]];
+  float4 output_4 [[color(2)]];
+  float4 output_5 [[color(4)]];
+};
+
+struct tint_symbol {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+struct Out {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+void f0(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 9u);
+}
+
+void f1(thread tint_private_vars_struct* const tint_private_vars) {
+  f0(tint_private_vars);
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 8u);
+}
+
+void f2(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 7u);
+  f1(tint_private_vars);
+}
+
+Out f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  f2(tint_private_vars);
+  Out const tint_symbol_1 = Out{.x=float4(10.0f), .y=float4(20.0f), .z=float4(30.0f)};
+  return tint_symbol_1;
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  Out const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.x = inner_result.x;
+  wrapper_result.y = inner_result.y;
+  wrapper_result.z = inner_result.z;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c, .output_3=result.x, .output_4=result.y, .output_5=result.z};
+}
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.spvasm
new file mode 100644
index 0000000..efb9a58
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.spvasm
@@ -0,0 +1,46 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.wgsl
new file mode 100644
index 0000000..d098380
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/multiple_attachments.wgsl.expected.wgsl
@@ -0,0 +1,38 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(4)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl
new file mode 100644
index 0000000..bccdc57
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl
@@ -0,0 +1,33 @@
+// flags: --pixel_local_attachments 0=1
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0) x : vec4f,
+  @location(2) y : vec4f,
+  @location(3) z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..217519c
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.dxc.hlsl
@@ -0,0 +1,44 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..217519c
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.fxc.hlsl
@@ -0,0 +1,44 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.glsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.glsl
new file mode 100644
index 0000000..4463a4e
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.glsl
@@ -0,0 +1,44 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.msl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.msl
new file mode 100644
index 0000000..25974dd
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.msl
@@ -0,0 +1,66 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  float4 output_1 [[color(0)]];
+  float4 output_2 [[color(2)]];
+  float4 output_3 [[color(3)]];
+};
+
+struct tint_symbol {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+struct Out {
+  float4 x;
+  float4 y;
+  float4 z;
+};
+
+void f0(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 9u);
+}
+
+void f1(thread tint_private_vars_struct* const tint_private_vars) {
+  f0(tint_private_vars);
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 8u);
+}
+
+void f2(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 7u);
+  f1(tint_private_vars);
+}
+
+Out f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  f2(tint_private_vars);
+  Out const tint_symbol_1 = Out{.x=float4(10.0f), .y=float4(20.0f), .z=float4(30.0f)};
+  return tint_symbol_1;
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  Out const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.x = inner_result.x;
+  wrapper_result.y = inner_result.y;
+  wrapper_result.z = inner_result.z;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=result.x, .output_2=result.y, .output_3=result.z};
+}
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.spvasm
new file mode 100644
index 0000000..bd27f7a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.spvasm
@@ -0,0 +1,44 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.wgsl
new file mode 100644
index 0000000..480fb58
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/multiple_outputs/single_attachment.wgsl.expected.wgsl
@@ -0,0 +1,36 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+struct Out {
+  @location(0)
+  x : vec4f,
+  @location(2)
+  y : vec4f,
+  @location(3)
+  z : vec4f,
+}
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> Out {
+  f2();
+  return Out(vec4f(10), vec4f(20), vec4f(30));
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl
new file mode 100644
index 0000000..cfc3b25
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl
@@ -0,0 +1,29 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..a32d13d
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.dxc.hlsl
@@ -0,0 +1,37 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..a32d13d
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.fxc.hlsl
@@ -0,0 +1,37 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.glsl b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.glsl
new file mode 100644
index 0000000..31f33e1
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.glsl
@@ -0,0 +1,37 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.msl b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.msl
new file mode 100644
index 0000000..8e356a7
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.msl
@@ -0,0 +1,57 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+  float4 output_3 [[color(0)]];
+};
+
+struct tint_symbol {
+  float4 value;
+};
+
+void f0(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 9u);
+}
+
+void f1(thread tint_private_vars_struct* const tint_private_vars) {
+  f0(tint_private_vars);
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 8u);
+}
+
+void f2(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 7u);
+  f1(tint_private_vars);
+}
+
+float4 f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  f2(tint_private_vars);
+  return float4(2.0f);
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  float4 const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c, .output_3=result.value};
+}
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.spvasm
new file mode 100644
index 0000000..feb0dc7
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.spvasm
@@ -0,0 +1,37 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.wgsl
new file mode 100644
index 0000000..63893e5
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/multiple_attachments.wgsl.expected.wgsl
@@ -0,0 +1,29 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl
new file mode 100644
index 0000000..fcab73b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl
@@ -0,0 +1,27 @@
+// flags: --pixel_local_attachments 0=1
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..4b6423a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.dxc.hlsl
@@ -0,0 +1,35 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..4b6423a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.fxc.hlsl
@@ -0,0 +1,35 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.glsl b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.glsl
new file mode 100644
index 0000000..2ed05a1
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.glsl
@@ -0,0 +1,35 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.msl b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.msl
new file mode 100644
index 0000000..8637100
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.msl
@@ -0,0 +1,53 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  float4 output_1 [[color(0)]];
+};
+
+struct tint_symbol {
+  float4 value;
+};
+
+void f0(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 9u);
+}
+
+void f1(thread tint_private_vars_struct* const tint_private_vars) {
+  f0(tint_private_vars);
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 8u);
+}
+
+void f2(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 7u);
+  f1(tint_private_vars);
+}
+
+float4 f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  f2(tint_private_vars);
+  return float4(2.0f);
+}
+
+tint_symbol f_inner_1(thread tint_private_vars_struct* const tint_private_vars) {
+  float4 const inner_result = f_inner(tint_private_vars);
+  tint_symbol wrapper_result = {};
+  wrapper_result.value = inner_result;
+  return wrapper_result;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  tint_symbol const result = f_inner_1(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=result.value};
+}
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.spvasm
new file mode 100644
index 0000000..5f1577b
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.spvasm
@@ -0,0 +1,35 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.wgsl
new file mode 100644
index 0000000..6f8ab7f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/one_output/single_attachment.wgsl.expected.wgsl
@@ -0,0 +1,27 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() -> @location(0) vec4f {
+  f2();
+  return vec4f(2);
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl
new file mode 100644
index 0000000..7bc316fe
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl
@@ -0,0 +1,28 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment fn f() {
+  f2();
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..961d901
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.dxc.hlsl
@@ -0,0 +1,36 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..961d901
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.fxc.hlsl
@@ -0,0 +1,36 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.glsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.glsl
new file mode 100644
index 0000000..c9fe6f1
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.glsl
@@ -0,0 +1,36 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.msl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.msl
new file mode 100644
index 0000000..6e843eb
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.msl
@@ -0,0 +1,44 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+  int b [[color(6)]];
+  float c [[color(3)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+  int output_1 [[color(6)]];
+  float output_2 [[color(3)]];
+};
+
+void f0(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 9u);
+}
+
+void f1(thread tint_private_vars_struct* const tint_private_vars) {
+  f0(tint_private_vars);
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 8u);
+}
+
+void f2(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 7u);
+  f1(tint_private_vars);
+}
+
+void f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  f2(tint_private_vars);
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a, .output_1=tint_private_vars.P.b, .output_2=tint_private_vars.P.c};
+}
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.spvasm
new file mode 100644
index 0000000..95c7a59
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.spvasm
@@ -0,0 +1,36 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.wgsl
new file mode 100644
index 0000000..48882e9
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/multiple_attachments.wgsl.expected.wgsl
@@ -0,0 +1,28 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+  b : i32,
+  c : f32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl
new file mode 100644
index 0000000..44fae7d
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl
@@ -0,0 +1,26 @@
+// flags: --pixel_local_attachments 0=1
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment fn f() {
+  f2();
+}
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..be9ab4f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.dxc.hlsl
@@ -0,0 +1,34 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..be9ab4f
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.fxc.hlsl
@@ -0,0 +1,34 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.glsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.glsl
new file mode 100644
index 0000000..1d46180
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.glsl
@@ -0,0 +1,34 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.msl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.msl
new file mode 100644
index 0000000..5c5a3a2
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.msl
@@ -0,0 +1,40 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal P;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+};
+
+void f0(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 9u);
+}
+
+void f1(thread tint_private_vars_struct* const tint_private_vars) {
+  f0(tint_private_vars);
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 8u);
+}
+
+void f2(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).P.a = ((*(tint_private_vars)).P.a + 7u);
+  f1(tint_private_vars);
+}
+
+void f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  f2(tint_private_vars);
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.P = pixel_local_1;
+  f_inner(&(tint_private_vars));
+  return {.output_0=tint_private_vars.P.a};
+}
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.spvasm
new file mode 100644
index 0000000..3aaefc3
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.spvasm
@@ -0,0 +1,34 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
+
+Failed to generate: extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.wgsl
new file mode 100644
index 0000000..77f85a0
--- /dev/null
+++ b/test/tint/extensions/pixel_local/indirect_use/zero_outputs/single_attachment.wgsl.expected.wgsl
@@ -0,0 +1,26 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> P : PixelLocal;
+
+fn f0() {
+  P.a += 9;
+}
+
+fn f1() {
+  f0();
+  P.a += 8;
+}
+
+fn f2() {
+  P.a += 7;
+  f1();
+}
+
+@fragment
+fn f() {
+  f2();
+}
diff --git a/test/tint/extensions/pixel_local/ptr/local.wgsl b/test/tint/extensions/pixel_local/ptr/local.wgsl
new file mode 100644
index 0000000..6f66cc7
--- /dev/null
+++ b/test/tint/extensions/pixel_local/ptr/local.wgsl
@@ -0,0 +1,13 @@
+// flags: --pixel_local_attachments 0=1,1=6,2=3
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> V : PixelLocal;
+
+@fragment fn f() {
+  let p = &V;
+  (*p).a = 42;
+}
diff --git a/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.dxc.hlsl b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.dxc.hlsl
new file mode 100644
index 0000000..f795a17
--- /dev/null
+++ b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.dxc.hlsl
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> V : PixelLocal;
+
+@fragment
+fn f() {
+  let p = &(V);
+  (*(p)).a = 42;
+}
+
+Failed to generate: extensions/pixel_local/ptr/local.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.fxc.hlsl b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.fxc.hlsl
new file mode 100644
index 0000000..f795a17
--- /dev/null
+++ b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.fxc.hlsl
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> V : PixelLocal;
+
+@fragment
+fn f() {
+  let p = &(V);
+  (*(p)).a = 42;
+}
+
+Failed to generate: extensions/pixel_local/ptr/local.wgsl:2:8 error: HLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.glsl b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.glsl
new file mode 100644
index 0000000..dcca860
--- /dev/null
+++ b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.glsl
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> V : PixelLocal;
+
+@fragment
+fn f() {
+  let p = &(V);
+  (*(p)).a = 42;
+}
+
+Failed to generate: extensions/pixel_local/ptr/local.wgsl:2:8 error: GLSL backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.msl b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.msl
new file mode 100644
index 0000000..97a249a
--- /dev/null
+++ b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.msl
@@ -0,0 +1,26 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct PixelLocal {
+  uint a [[color(1)]];
+};
+
+struct tint_private_vars_struct {
+  PixelLocal V;
+};
+
+struct f_res {
+  uint output_0 [[color(1)]];
+};
+
+void f_inner(thread tint_private_vars_struct* const tint_private_vars) {
+  (*(tint_private_vars)).V.a = 42u;
+}
+
+fragment f_res f(PixelLocal pixel_local_1) {
+  thread tint_private_vars_struct tint_private_vars = {};
+  tint_private_vars.V = pixel_local_1;
+  f_inner(&(tint_private_vars));
+  return {.output_0=tint_private_vars.V.a};
+}
+
diff --git a/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.spvasm b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.spvasm
new file mode 100644
index 0000000..76d9d07
--- /dev/null
+++ b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.spvasm
@@ -0,0 +1,21 @@
+SKIP: FAILED
+
+
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> V : PixelLocal;
+
+@fragment
+fn f() {
+  let p = &(V);
+  (*(p)).a = 42;
+}
+
+Failed to generate: extensions/pixel_local/ptr/local.wgsl:2:8 error: SPIR-V backend does not support extension 'chromium_experimental_pixel_local'
+enable chromium_experimental_pixel_local;
+       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
diff --git a/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.wgsl b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.wgsl
new file mode 100644
index 0000000..66c3329
--- /dev/null
+++ b/test/tint/extensions/pixel_local/ptr/local.wgsl.expected.wgsl
@@ -0,0 +1,13 @@
+enable chromium_experimental_pixel_local;
+
+struct PixelLocal {
+  a : u32,
+}
+
+var<pixel_local> V : PixelLocal;
+
+@fragment
+fn f() {
+  let p = &(V);
+  (*(p)).a = 42;
+}