[tint] Add 'pixel_local' address space

Bug: dawn:1704
Change-Id: Ib0bfadb0fb02e1b17131019dccfd1b100eebbdef
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/149687
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6263ad7..7926253 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -188,6 +188,9 @@
 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_LOCAL_STORAGE_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/scripts/tint_overrides_with_defaults.gni b/scripts/tint_overrides_with_defaults.gni
index f900529..6d81294 100644
--- a/scripts/tint_overrides_with_defaults.gni
+++ b/scripts/tint_overrides_with_defaults.gni
@@ -91,6 +91,11 @@
   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_local_storage_extension)) {
+    tint_enable_local_storage_extension = false
+  }
 }
 
 declare_args() {
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 8a7dc95..a46d612 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -95,6 +95,13 @@
     defines += [ "TINT_BUILD_IR=0" ]
   }
 
+  # TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
+  if (tint_enable_local_storage_extension) {
+    defines += [ "TINT_ENABLE_LOCAL_STORAGE_EXTENSION=1" ]
+  } else {
+    defines += [ "TINT_ENABLE_LOCAL_STORAGE_EXTENSION=0" ]
+  }
+
   include_dirs = [
     "${tint_root_dir}/",
     "${tint_root_dir}/include/",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 9308745..47c81e6 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -44,6 +44,9 @@
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_SYNTAX_TREE_WRITER=$<BOOL:${TINT_BUILD_SYNTAX_TREE_WRITER}>)
   target_compile_definitions(${TARGET} PUBLIC -DTINT_BUILD_IR=$<BOOL:${TINT_BUILD_IR}>)
 
+  # TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is production-ready
+  target_compile_definitions(${TARGET} PUBLIC -DTINT_ENABLE_LOCAL_STORAGE_EXTENSION=$<BOOL:${TINT_ENABLE_LOCAL_STORAGE_EXTENSION}>)
+
   common_compile_options(${TARGET})
 endfunction()
 
diff --git a/src/tint/lang/core/address_space.cc b/src/tint/lang/core/address_space.cc
index efc87a6..c09e8a7 100644
--- a/src/tint/lang/core/address_space.cc
+++ b/src/tint/lang/core/address_space.cc
@@ -38,6 +38,9 @@
     if (str == "function") {
         return AddressSpace::kFunction;
     }
+    if (str == "pixel_local") {
+        return AddressSpace::kPixelLocal;
+    }
     if (str == "private") {
         return AddressSpace::kPrivate;
     }
@@ -68,6 +71,8 @@
             return "function";
         case AddressSpace::kHandle:
             return "handle";
+        case AddressSpace::kPixelLocal:
+            return "pixel_local";
         case AddressSpace::kPrivate:
             return "private";
         case AddressSpace::kPushConstant:
diff --git a/src/tint/lang/core/address_space.h b/src/tint/lang/core/address_space.h
index d88857f..9c98159 100644
--- a/src/tint/lang/core/address_space.h
+++ b/src/tint/lang/core/address_space.h
@@ -37,6 +37,7 @@
     kOut,
     kFunction,
     kHandle,  // Tint-internal enum entry - not parsed
+    kPixelLocal,
     kPrivate,
     kPushConstant,
     kStorage,
@@ -62,7 +63,8 @@
 AddressSpace ParseAddressSpace(std::string_view str);
 
 constexpr const char* kAddressSpaceStrings[] = {
-    "__in", "__out", "function", "private", "push_constant", "storage", "uniform", "workgroup",
+    "__in",          "__out",   "function", "pixel_local", "private",
+    "push_constant", "storage", "uniform",  "workgroup",
 };
 
 /// @returns true if the AddressSpace is host-shareable
diff --git a/src/tint/lang/core/address_space_bench.cc b/src/tint/lang/core/address_space_bench.cc
index b2933d6..6ccc78d 100644
--- a/src/tint/lang/core/address_space_bench.cc
+++ b/src/tint/lang/core/address_space_bench.cc
@@ -53,41 +53,48 @@
         "funEtion",
         "PPncTTion",
         "xxuncddon",
-        "p44ivate",
-        "prSSvaVVe",
-        "RriR22e",
+        "pixe44_local",
+        "SSVVxel_local",
+        "pixRR_local",
+        "pixel_local",
+        "pixel_lF9a",
+        "pixel_loca",
+        "pOOxVRl_locH",
+        "prvaye",
+        "llnrrrv77te",
+        "priv4t00",
         "private",
-        "pFva9e",
-        "priate",
-        "VOORRHte",
-        "push_constyn",
-        "punnh_crr77stallt",
-        "pu4h_cons00ant",
+        "rvooe",
+        "zzvate",
+        "piiippa1",
+        "puXXh_constant",
+        "pusII9_nn55nstant",
+        "YusHH_coaastSSrnt",
         "push_constant",
-        "puoo_costan",
-        "ushzzcnstant",
-        "push_coii11apt",
-        "storaXXe",
-        "9II5tnnrage",
-        "stoaSSrHHYe",
+        "pushonkkHan",
+        "jush_consgRt",
+        "puh_cobsant",
+        "storaje",
+        "torage",
+        "qrage",
         "storage",
-        "stkke",
-        "jtogRa",
-        "sbrag",
+        "stoNNge",
+        "torgvv",
+        "QQorage",
+        "unffor",
         "unifojm",
-        "niform",
-        "qform",
+        "uNNwfor8",
         "uniform",
-        "uniNNrm",
-        "nifrvv",
-        "QQiform",
-        "workrorf",
-        "workjroup",
-        "wNNorkrou2",
+        "uniorm",
+        "urriform",
+        "Gniform",
+        "workgrFFup",
+        "Eokgru",
+        "worrgroup",
         "workgroup",
-        "workgrop",
-        "rrorkgroup",
-        "workgroGp",
+        "wokgrou",
+        "woJDkgoup",
+        "okroup",
     };
     for (auto _ : state) {
         for (auto* str : kStrings) {
diff --git a/src/tint/lang/core/address_space_test.cc b/src/tint/lang/core/address_space_test.cc
index fa78e4c..c17b37b 100644
--- a/src/tint/lang/core/address_space_test.cc
+++ b/src/tint/lang/core/address_space_test.cc
@@ -47,6 +47,7 @@
     {"__in", AddressSpace::kIn},
     {"__out", AddressSpace::kOut},
     {"function", AddressSpace::kFunction},
+    {"pixel_local", AddressSpace::kPixelLocal},
     {"private", AddressSpace::kPrivate},
     {"push_constant", AddressSpace::kPushConstant},
     {"storage", AddressSpace::kStorage},
@@ -55,18 +56,20 @@
 };
 
 static constexpr Case kInvalidCases[] = {
-    {"ccin", AddressSpace::kUndefined},          {"3", AddressSpace::kUndefined},
-    {"_Vin", AddressSpace::kUndefined},          {"__ou1", AddressSpace::kUndefined},
-    {"qq_Jt", AddressSpace::kUndefined},         {"__oll7t", AddressSpace::kUndefined},
-    {"qquntppHon", AddressSpace::kUndefined},    {"cnciv", AddressSpace::kUndefined},
-    {"funGion", AddressSpace::kUndefined},       {"priviive", AddressSpace::kUndefined},
-    {"8WWivate", AddressSpace::kUndefined},      {"pxxvate", AddressSpace::kUndefined},
-    {"pXh_cggnstant", AddressSpace::kUndefined}, {"pX_Vonstanu", AddressSpace::kUndefined},
-    {"push_consta3t", AddressSpace::kUndefined}, {"Etorage", AddressSpace::kUndefined},
-    {"sPTTrage", AddressSpace::kUndefined},      {"storadxx", AddressSpace::kUndefined},
-    {"u44iform", AddressSpace::kUndefined},      {"unSSfoVVm", AddressSpace::kUndefined},
-    {"RniR22m", AddressSpace::kUndefined},       {"w9rFroup", AddressSpace::kUndefined},
-    {"workgoup", AddressSpace::kUndefined},      {"woVROOrHup", AddressSpace::kUndefined},
+    {"ccin", AddressSpace::kUndefined},           {"3", AddressSpace::kUndefined},
+    {"_Vin", AddressSpace::kUndefined},           {"__ou1", AddressSpace::kUndefined},
+    {"qq_Jt", AddressSpace::kUndefined},          {"__oll7t", AddressSpace::kUndefined},
+    {"qquntppHon", AddressSpace::kUndefined},     {"cnciv", AddressSpace::kUndefined},
+    {"funGion", AddressSpace::kUndefined},        {"pivel_liical", AddressSpace::kUndefined},
+    {"pixel_lWW8al", AddressSpace::kUndefined},   {"piel_xxoMal", AddressSpace::kUndefined},
+    {"pXvatgg", AddressSpace::kUndefined},        {"rvaXe", AddressSpace::kUndefined},
+    {"priv3te", AddressSpace::kUndefined},        {"push_constanE", AddressSpace::kUndefined},
+    {"push_TTPnstant", AddressSpace::kUndefined}, {"puxxdh_constan", AddressSpace::kUndefined},
+    {"s44orage", AddressSpace::kUndefined},       {"stSSraVVe", AddressSpace::kUndefined},
+    {"RtoR22e", AddressSpace::kUndefined},        {"uFfo9m", AddressSpace::kUndefined},
+    {"uniorm", AddressSpace::kUndefined},         {"VOORRHrm", AddressSpace::kUndefined},
+    {"woykgoup", AddressSpace::kUndefined},       {"l77nnrrkgroGp", AddressSpace::kUndefined},
+    {"wo4kgr00up", AddressSpace::kUndefined},
 };
 
 using AddressSpaceParseTest = testing::TestWithParam<Case>;
diff --git a/src/tint/lang/core/core.def b/src/tint/lang/core/core.def
index c346f97..de0c81e 100644
--- a/src/tint/lang/core/core.def
+++ b/src/tint/lang/core/core.def
@@ -96,6 +96,7 @@
   uniform
   storage
   push_constant
+  pixel_local
   __in
   __out
   @internal handle
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_type_test.cc b/src/tint/lang/spirv/writer/ast_printer/ast_type_test.cc
index 2aec6e5..a1b3ce6 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_type_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_type_test.cc
@@ -628,8 +628,7 @@
 INSTANTIATE_TEST_SUITE_P(
     SpirvASTPrinterTest_Type,
     PtrDataTest,
-    testing::Values(PtrData{core::AddressSpace::kUndefined, SpvStorageClassMax},
-                    PtrData{core::AddressSpace::kIn, SpvStorageClassInput},
+    testing::Values(PtrData{core::AddressSpace::kIn, SpvStorageClassInput},
                     PtrData{core::AddressSpace::kOut, SpvStorageClassOutput},
                     PtrData{core::AddressSpace::kUniform, SpvStorageClassUniform},
                     PtrData{core::AddressSpace::kWorkgroup, SpvStorageClassWorkgroup},
diff --git a/src/tint/lang/spirv/writer/ast_printer/builder.cc b/src/tint/lang/spirv/writer/ast_printer/builder.cc
index fc8b55e..a06081e 100644
--- a/src/tint/lang/spirv/writer/ast_printer/builder.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/builder.cc
@@ -3968,8 +3968,8 @@
     return true;
 }
 
-SpvStorageClass Builder::ConvertAddressSpace(core::AddressSpace klass) const {
-    switch (klass) {
+SpvStorageClass Builder::ConvertAddressSpace(core::AddressSpace address_space) const {
+    switch (address_space) {
         case core::AddressSpace::kIn:
             return SpvStorageClassInput;
         case core::AddressSpace::kOut:
@@ -3988,9 +3988,12 @@
             return SpvStorageClassPrivate;
         case core::AddressSpace::kFunction:
             return SpvStorageClassFunction;
+
+        case core::AddressSpace::kPixelLocal:
         case core::AddressSpace::kUndefined:
             break;
     }
+    TINT_UNREACHABLE() << "unhandled address space '" << address_space << "'";
     return SpvStorageClassMax;
 }
 
diff --git a/src/tint/lang/wgsl/ast/builder.h b/src/tint/lang/wgsl/ast/builder.h
index 872e20c..e575633 100644
--- a/src/tint/lang/wgsl/ast/builder.h
+++ b/src/tint/lang/wgsl/ast/builder.h
@@ -1727,8 +1727,8 @@
     /// @param options the extra options passed to the ast::Var initializer
     /// Can be any of the following, in any order:
     ///   * ast::Type           - specifies the variable's type
-    ///   * core::AddressSpace   - specifies the variable address space
-    ///   * core::Access         - specifies the variable's access control
+    ///   * core::AddressSpace  - specifies the variable address space
+    ///   * core::Access        - specifies the variable's access control
     ///   * ast::Expression*    - specifies the variable's initializer expression
     ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
     /// Note that non-repeatable arguments of the same type will use the last argument's value.
@@ -1744,10 +1744,10 @@
     /// @param options the extra options passed to the ast::Var initializer
     /// Can be any of the following, in any order:
     ///   * ast::Type           - specifies the variable's type
-    ///   * core::AddressSpace   - specifies the variable address space
-    ///   * core::Access         - specifies the variable's access control
+    ///   * core::AddressSpace  - specifies the variable address space
+    ///   * core::Access        - specifies the variable's access control
     ///   * ast::Expression*    - specifies the variable's initializer expression
-    ///   * ast::Attribute*    - specifies the variable's attributes (repeatable, or vector)
+    ///   * ast::Attribute*     - specifies the variable's attributes (repeatable, or vector)
     /// Note that non-repeatable arguments of the same type will use the last argument's value.
     /// @returns a new `ast::Var`, which is automatically registered as a global variable with the
     /// ast::Module.
diff --git a/src/tint/lang/wgsl/resolver/BUILD.cmake b/src/tint/lang/wgsl/resolver/BUILD.cmake
index 6f0a3d3..a2e516c 100644
--- a/src/tint/lang/wgsl/resolver/BUILD.cmake
+++ b/src/tint/lang/wgsl/resolver/BUILD.cmake
@@ -106,6 +106,7 @@
   lang/wgsl/resolver/load_test.cc
   lang/wgsl/resolver/materialize_test.cc
   lang/wgsl/resolver/override_test.cc
+  lang/wgsl/resolver/pixel_local_extension_test.cc
   lang/wgsl/resolver/ptr_ref_test.cc
   lang/wgsl/resolver/ptr_ref_validation_test.cc
   lang/wgsl/resolver/resolver_behavior_test.cc
diff --git a/src/tint/lang/wgsl/resolver/BUILD.gn b/src/tint/lang/wgsl/resolver/BUILD.gn
index 2e40118..319ea05 100644
--- a/src/tint/lang/wgsl/resolver/BUILD.gn
+++ b/src/tint/lang/wgsl/resolver/BUILD.gn
@@ -109,6 +109,7 @@
       "load_test.cc",
       "materialize_test.cc",
       "override_test.cc",
+      "pixel_local_extension_test.cc",
       "ptr_ref_test.cc",
       "ptr_ref_validation_test.cc",
       "resolver_behavior_test.cc",
diff --git a/src/tint/lang/wgsl/resolver/function_validation_test.cc b/src/tint/lang/wgsl/resolver/function_validation_test.cc
index caf20dc..dfeae7a 100644
--- a/src/tint/lang/wgsl/resolver/function_validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/function_validation_test.cc
@@ -1052,6 +1052,16 @@
     auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
     Func("f", Vector{arg}, ty.void_(), tint::Empty);
 
+    if (param.address_space == core::AddressSpace::kPixelLocal) {
+#if !TINT_ENABLE_LOCAL_STORAGE_EXTENSION
+        // TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is
+        // production-ready
+        GTEST_SKIP() << "requires TINT_ENABLE_LOCAL_STORAGE_EXTENSION";
+#else
+        Enable(core::Extension::kChromiumExperimentalPixelLocal);
+#endif
+    }
+
     if (param.expectation == Expectation::kAlwaysPass) {
         ASSERT_TRUE(r()->Resolve()) << r()->error();
     } else {
@@ -1060,7 +1070,7 @@
         EXPECT_FALSE(r()->Resolve());
         if (param.expectation == Expectation::kInvalid) {
             std::string err = R"(12:34 error: unresolved address space '${addr_space}'
-12:34 note: Possible values: 'function', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')";
+12:34 note: Possible values: 'function', 'pixel_local', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')";
             err = tint::ReplaceAll(err, "${addr_space}", tint::ToString(param.address_space));
             EXPECT_EQ(r()->error(), err);
         } else {
@@ -1070,13 +1080,23 @@
         }
     }
 }
-TEST_P(ResolverFunctionParameterValidationTest, AddressSpaceWithExtension) {
+TEST_P(ResolverFunctionParameterValidationTest, AddressSpaceWithFullPtrParameterExtension) {
     auto& param = GetParam();
     auto ptr_type = ty("ptr", Ident(Source{{12, 34}}, param.address_space), ty.i32());
     auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
     Enable(core::Extension::kChromiumExperimentalFullPtrParameters);
     Func("f", Vector{arg}, ty.void_(), tint::Empty);
 
+    if (param.address_space == core::AddressSpace::kPixelLocal) {
+#if !TINT_ENABLE_LOCAL_STORAGE_EXTENSION
+        // TODO(crbug.com/dawn/1704): Remove when chromium_experimental_pixel_local is
+        // production-ready
+        GTEST_SKIP() << "requires TINT_ENABLE_LOCAL_STORAGE_EXTENSION";
+#else
+        Enable(core::Extension::kChromiumExperimentalPixelLocal);
+#endif
+    }
+
     if (param.expectation == Expectation::kAlwaysPass ||
         param.expectation == Expectation::kPassWithFullPtrParameterExtension) {
         ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -1084,7 +1104,7 @@
         EXPECT_FALSE(r()->Resolve());
         if (param.expectation == Expectation::kInvalid) {
             std::string err = R"(12:34 error: unresolved address space '${addr_space}'
-12:34 note: Possible values: 'function', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')";
+12:34 note: Possible values: 'function', 'pixel_local', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')";
             err = tint::ReplaceAll(err, "${addr_space}", tint::ToString(param.address_space));
             EXPECT_EQ(r()->error(), err);
         } else {
@@ -1105,6 +1125,7 @@
         TestParams{core::AddressSpace::kWorkgroup, Expectation::kPassWithFullPtrParameterExtension},
         TestParams{core::AddressSpace::kHandle, Expectation::kInvalid},
         TestParams{core::AddressSpace::kStorage, Expectation::kPassWithFullPtrParameterExtension},
+        TestParams{core::AddressSpace::kPixelLocal, Expectation::kAlwaysFail},
         TestParams{core::AddressSpace::kPrivate, Expectation::kAlwaysPass},
         TestParams{core::AddressSpace::kFunction, 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
new file mode 100644
index 0000000..b10f1c1
--- /dev/null
+++ b/src/tint/lang/wgsl/resolver/pixel_local_extension_test.cc
@@ -0,0 +1,63 @@
+// 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/wgsl/resolver/resolver.h"
+#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"
+
+#include "gmock/gmock.h"
+
+namespace tint::resolver {
+namespace {
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+using ResolverPixelLocalExtensionTest = ResolverTest;
+
+TEST_F(ResolverPixelLocalExtensionTest, AddressSpaceUsedWithExtension) {
+    // enable chromium_experimental_pixel_local;
+    // var<pixel_local> v : f16;
+    Enable(Source{{12, 34}}, core::Extension::kChromiumExperimentalPixelLocal);
+
+    GlobalVar("v", ty.u32(), core::AddressSpace::kPixelLocal);
+
+#if TINT_ENABLE_LOCAL_STORAGE_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_LOCAL_STORAGE_EXTENSION)");
+#endif
+}
+
+TEST_F(ResolverPixelLocalExtensionTest, AddressSpaceUsedWithoutExtension) {
+    // var<pixel_local> v : u32;
+    AST().AddGlobalVariable(create<ast::Var>(
+        /* name */ Ident("v"),
+        /* type */ ty.u32(),
+        /* declared_address_space */ Expr(Source{{12, 34}}, core::AddressSpace::kPixelLocal),
+        /* declared_access */ nullptr,
+        /* initializer */ nullptr,
+        /* attributes */ Empty));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: 'pixel_local' address space requires the 'chromium_experimental_pixel_local' extension enabled)");
+}
+
+}  // namespace
+}  // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index 1219e92..2e75b15 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -529,7 +529,7 @@
     auto address_space = core::AddressSpace::kUndefined;
     if (var->declared_address_space) {
         auto expr = AddressSpaceExpression(var->declared_address_space);
-        if (!expr) {
+        if (TINT_UNLIKELY(!expr)) {
             return nullptr;
         }
         address_space = expr->Value();
@@ -1602,7 +1602,20 @@
 sem::BuiltinEnumExpression<core::AddressSpace>* Resolver::AddressSpaceExpression(
     const ast::Expression* expr) {
     identifier_resolve_hint_ = {expr, "address space", core::kAddressSpaceStrings};
-    return sem_.AsAddressSpace(Expression(expr));
+    auto address_space_expr = sem_.AsAddressSpace(Expression(expr));
+    if (TINT_UNLIKELY(!address_space_expr)) {
+        return nullptr;
+    }
+    if (TINT_UNLIKELY(
+            address_space_expr->Value() == core::AddressSpace::kPixelLocal &&
+            !enabled_extensions_.Contains(core::Extension::kChromiumExperimentalPixelLocal))) {
+        StringStream err;
+        err << "'pixel_local' address space requires the '"
+            << core::Extension::kChromiumExperimentalPixelLocal << "' extension enabled";
+        AddError(err.str(), expr->source);
+        return nullptr;
+    }
+    return address_space_expr;
 }
 
 sem::BuiltinEnumExpression<core::BuiltinValue>* Resolver::BuiltinValueExpression(
@@ -3984,6 +3997,16 @@
     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_LOCAL_STORAGE_EXTENSION
+        if (ext->name == core::Extension::kChromiumExperimentalPixelLocal) {
+            AddError(std::string(core::ToString(core::Extension::kChromiumExperimentalPixelLocal)) +
+                         " requires TINT_ENABLE_LOCAL_STORAGE_EXTENSION",
+                     enable->source);
+            return false;
+        }
+#endif
     }
     return true;
 }
diff --git a/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc b/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
index 173934f..041a74b 100644
--- a/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
+++ b/src/tint/lang/wgsl/resolver/unresolved_identifier_test.cc
@@ -38,7 +38,7 @@
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(12:34 error: unresolved address space 'privte'
 12:34 note: Did you mean 'private'?
-Possible values: 'function', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')");
+Possible values: 'function', 'pixel_local', 'private', 'push_constant', 'storage', 'uniform', 'workgroup')");
 }
 
 TEST_F(ResolverUnresolvedIdentifierSuggestions, BuiltinValue) {