[spirv-reader] Add support for read-write textures

Generate a read-write storage texture if a texture is both read from
and written to. This enables the
`chromium_experimental_read_write_storage_texture` extension.

Add an option to the SPIR-V reader that enables the use of
Chromium-specific extensions, defaulting to `false`.

Bug: tint:2007
Change-Id: I55b768aa8cbb9ee22a3722177f813cb04a049fc0
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/147120
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 2e78452..50ec18e 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -2009,6 +2009,7 @@
       "lang/spirv/reader/ast_parser/module_var_test.cc",
       "lang/spirv/reader/ast_parser/named_types_test.cc",
       "lang/spirv/reader/ast_parser/namer_test.cc",
+      "lang/spirv/reader/ast_parser/parser_test.cc",
       "lang/spirv/reader/ast_parser/spirv_tools_helpers_test.cc",
       "lang/spirv/reader/ast_parser/spirv_tools_helpers_test.h",
       "lang/spirv/reader/ast_parser/type_test.cc",
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 2520816..57f2662 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -313,6 +313,14 @@
             opts->spirv_reader_options.allow_non_uniform_derivatives = true;
         }
     });
+    auto& allow_chromium_extensions = options.Add<BoolOption>(
+        "allow-chromium-extensions",
+        "When using SPIR-V input, allow the use of Chromium-specific extensions", Default{false});
+    TINT_DEFER({
+        if (allow_chromium_extensions.value.value_or(false)) {
+            opts->spirv_reader_options.allow_chromium_extensions = true;
+        }
+    });
 #endif
 
     auto& disable_wg_init = options.Add<BoolOption>(
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
index 70fa074..69995f6 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
@@ -2542,9 +2542,8 @@
             return nullptr;
         }
 
-        // WGSL textures are always formatted.  Unformatted textures are always
-        // sampled.
-        if (usage.IsSampledTexture() || usage.IsStorageReadTexture() ||
+        // WGSL storage textures are always formatted.  Unformatted textures are always sampled.
+        if (usage.IsSampledTexture() || usage.IsStorageReadOnlyTexture() ||
             (uint32_t(image_type->format()) == uint32_t(spv::ImageFormat::Unknown))) {
             // Make a sampled texture type.
             auto* ast_sampled_component_type =
@@ -2572,7 +2571,11 @@
                 ast_handle_type = ty_.SampledTexture(dim, ast_sampled_component_type);
             }
         } else {
-            const auto access = core::Access::kWrite;
+            const auto access =
+                usage.IsStorageReadWriteTexture() ? core::Access::kReadWrite : core::Access::kWrite;
+            if (access == core::Access::kReadWrite) {
+                Enable(core::Extension::kChromiumExperimentalReadWriteStorageTexture);
+            }
             const auto format = enum_converter_.ToTexelFormat(image_type->format());
             if (format == core::TexelFormat::kUndefined) {
                 return nullptr;
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser.h b/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
index f8304a7..bf5df6b 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
@@ -738,6 +738,14 @@
     /// @returns the SPIR-V binary.
     const std::vector<uint32_t>& spv_binary() { return spv_binary_; }
 
+    /// Enable a WGSL extension, if not already enabled.
+    /// @param extension the extension to enable
+    void Enable(core::Extension extension) {
+        if (enabled_extensions_.Add(extension)) {
+            builder_.Enable(extension);
+        }
+    }
+
   private:
     /// Converts a specific SPIR-V type to a Tint type. Integer case
     const Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
@@ -925,6 +933,9 @@
     /// field will be 0. Sadly, in SPIR-V right now, there's only one workgroup
     /// size object in the module.
     WorkgroupSizeInfo workgroup_size_builtin_;
+
+    /// Set of WGSL extensions that have been enabled.
+    Hashset<core::Extension, 4> enabled_extensions_;
 };
 
 }  // namespace tint::spirv::reader::ast_parser
diff --git a/src/tint/lang/spirv/reader/ast_parser/function.cc b/src/tint/lang/spirv/reader/ast_parser/function.cc
index 658652d..3cb3a66f 100644
--- a/src/tint/lang/spirv/reader/ast_parser/function.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/function.cc
@@ -5633,9 +5633,10 @@
         image_operands_mask ^= uint32_t(spv::ImageOperandsMask::Lod);
         arg_index++;
     } else if ((op == spv::Op::OpImageFetch || op == spv::Op::OpImageRead) &&
-               !texture_type->IsAnyOf<DepthMultisampledTexture, MultisampledTexture>()) {
-        // textureLoad requires an explicit level-of-detail parameter for
-        // non-multisampled texture types.
+               !texture_type
+                    ->IsAnyOf<DepthMultisampledTexture, MultisampledTexture, StorageTexture>()) {
+        // textureLoad requires an explicit level-of-detail parameter for non-multisampled and
+        // non-storage texture types.
         args.Push(parser_impl_.MakeNullValue(ty_.I32()));
     }
     if (arg_index + 1 < num_args &&
diff --git a/src/tint/lang/spirv/reader/ast_parser/handle_test.cc b/src/tint/lang/spirv/reader/ast_parser/handle_test.cc
index 8fb26ed..fcc1ca8 100644
--- a/src/tint/lang/spirv/reader/ast_parser/handle_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/handle_test.cc
@@ -4181,6 +4181,65 @@
     ASSERT_EQ(expect, got);
 }
 
+TEST_F(SpvParserHandleTest, ReadWriteStorageTexture) {
+    const auto assembly = Preamble() + R"(
+               OpCapability Shader
+               OpCapability StorageImageExtendedFormats
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %100 "main"
+               OpExecutionMode %100 LocalSize 8 8 1
+               OpSource HLSL 600
+               OpName %type_2d_image "type.2d.image"
+               OpName %RWTexture2D "RWTexture2D"
+               OpName %100 "main"
+               OpDecorate %RWTexture2D DescriptorSet 0
+               OpDecorate %RWTexture2D Binding 0
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %v4float = OpTypeVector %float 4
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+     %v2uint = OpTypeVector %uint 2
+      %coord = OpConstantComposite %v2uint %uint_1 %uint_1
+%type_2d_image = OpTypeImage %float 2D 2 0 0 2 Rgba32f
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void
+%RWTexture2D = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+        %100 = OpFunction %void None %20
+         %22 = OpLabel
+         %30 = OpLoad %type_2d_image %RWTexture2D
+         %31 = OpImageRead %v4float %30 %coord None
+         %32 = OpFAdd %v4float %31 %31
+               OpImageWrite %30 %coord %32 None
+               OpReturn
+               OpFunctionEnd
+  )";
+    auto p = parser(test::Assemble(assembly));
+    EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error() << assembly;
+
+    EXPECT_TRUE(p->error().empty()) << p->error();
+    const auto got = test::ToString(p->program());
+    auto* expect = R"(enable chromium_experimental_read_write_storage_texture;
+
+@group(0) @binding(0) var RWTexture2D : texture_storage_2d<rgba32float, read_write>;
+
+const x_9 = vec2u(1u);
+
+fn main_1() {
+  let x_31 = textureLoad(RWTexture2D, vec2i(x_9));
+  textureStore(RWTexture2D, vec2i(x_9), (x_31 + x_31));
+  return;
+}
+
+@compute @workgroup_size(8i, 8i, 1i)
+fn main() {
+  main_1();
+}
+)";
+    ASSERT_EQ(expect, got);
+}
+
 TEST_F(SpvParserHandleTest, SimpleSelectCanSelectFromHoistedConstant) {
     // Demonstrates fix for crbug.com/tint/1642
     // The problem is an operand to a simple select can be a value
diff --git a/src/tint/lang/spirv/reader/ast_parser/parse.cc b/src/tint/lang/spirv/reader/ast_parser/parse.cc
index 27dc272..7b60434 100644
--- a/src/tint/lang/spirv/reader/ast_parser/parse.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/parse.cc
@@ -47,6 +47,33 @@
         builder.DiagnosticDirective(core::DiagnosticSeverity::kOff, "derivative_uniformity");
     }
 
+    if (!options.allow_chromium_extensions) {
+        // Check if any Chromium extensions were used.
+        for (auto* enable : builder.AST().Enables()) {
+            for (auto* extension : enable->extensions) {
+                switch (extension->name) {
+                    case core::Extension::kUndefined:
+                    case core::Extension::kChromiumDisableUniformityAnalysis:
+                    case core::Extension::kChromiumExperimentalDp4A:
+                    case core::Extension::kChromiumExperimentalFullPtrParameters:
+                    case core::Extension::kChromiumExperimentalPushConstant:
+                    case core::Extension::kChromiumExperimentalReadWriteStorageTexture:
+                    case core::Extension::kChromiumExperimentalSubgroups:
+                    case core::Extension::kChromiumInternalDualSourceBlending:
+                    case core::Extension::kChromiumInternalRelaxedUniformLayout: {
+                        StringStream ss;
+                        ss << "module requires " << ToString(extension->name)
+                           << ", but 'allow-chromium-extensions' was not passed";
+                        builder.Diagnostics().add_error(diag::System::Reader, ss.str());
+                        return Program(std::move(builder));
+                    }
+                    case core::Extension::kF16:
+                        break;
+                }
+            }
+        }
+    }
+
     // The SPIR-V parser can construct disjoint AST nodes, which is invalid for
     // the Resolver. Clone the Program to clean these up.
     Program program_with_disjoint_ast(std::move(builder));
diff --git a/src/tint/lang/spirv/reader/ast_parser/parser_test.cc b/src/tint/lang/spirv/reader/ast_parser/parser_test.cc
index 29c222a..d5f8c90 100644
--- a/src/tint/lang/spirv/reader/ast_parser/parser_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/parser_test.cc
@@ -78,6 +78,63 @@
     EXPECT_EQ(program.Diagnostics().count(), 0u) << errs;
 }
 
+constexpr auto kShaderWithReadWriteStorageTexture = R"(
+               OpCapability Shader
+               OpCapability StorageImageExtendedFormats
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %100 "main"
+               OpExecutionMode %100 LocalSize 8 8 1
+               OpSource HLSL 600
+               OpName %type_2d_image "type.2d.image"
+               OpName %RWTexture2D "RWTexture2D"
+               OpName %100 "main"
+               OpDecorate %RWTexture2D DescriptorSet 0
+               OpDecorate %RWTexture2D Binding 0
+      %float = OpTypeFloat 32
+    %float_0 = OpConstant %float 0
+    %v4float = OpTypeVector %float 4
+       %uint = OpTypeInt 32 0
+     %uint_1 = OpConstant %uint 1
+     %v2uint = OpTypeVector %uint 2
+      %coord = OpConstantComposite %v2uint %uint_1 %uint_1
+%type_2d_image = OpTypeImage %float 2D 2 0 0 2 Rgba32f
+%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image
+       %void = OpTypeVoid
+         %20 = OpTypeFunction %void
+%RWTexture2D = OpVariable %_ptr_UniformConstant_type_2d_image UniformConstant
+        %100 = OpFunction %void None %20
+         %22 = OpLabel
+         %30 = OpLoad %type_2d_image %RWTexture2D
+         %31 = OpImageRead %v4float %30 %coord None
+         %32 = OpFAdd %v4float %31 %31
+               OpImageWrite %30 %coord %32 None
+               OpReturn
+               OpFunctionEnd
+  )";
+
+TEST_F(ParserTest, AllowChromiumExtensions_False) {
+    auto spv = test::Assemble(kShaderWithReadWriteStorageTexture);
+    Options options;
+    options.allow_chromium_extensions = false;
+    auto program = Parse(spv, options);
+    auto errs = program.Diagnostics().str();
+    EXPECT_FALSE(program.IsValid()) << errs;
+    EXPECT_THAT(errs,
+                ::testing::HasSubstr(
+                    "error: module requires chromium_experimental_read_write_storage_texture, but "
+                    "'allow-chromium-extensions' was not passed"));
+}
+
+TEST_F(ParserTest, AllowChromiumExtensions_True) {
+    auto spv = test::Assemble(kShaderWithReadWriteStorageTexture);
+    Options options;
+    options.allow_chromium_extensions = true;
+    auto program = Parse(spv, options);
+    auto errs = program.Diagnostics().str();
+    EXPECT_TRUE(program.IsValid()) << errs;
+    EXPECT_EQ(program.Diagnostics().count(), 0u) << errs;
+}
+
 // TODO(dneto): uint32 vec, valid SPIR-V
 // TODO(dneto): uint32 vec, invalid SPIR-V
 
diff --git a/src/tint/lang/spirv/reader/ast_parser/usage.cc b/src/tint/lang/spirv/reader/ast_parser/usage.cc
index 0a67cac..910024a 100644
--- a/src/tint/lang/spirv/reader/ast_parser/usage.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/usage.cc
@@ -98,11 +98,6 @@
                 return false;
             }
         }
-
-        // Can't be both read and write.  This is a restriction in WebGPU.
-        if (is_storage_read_ && is_storage_write_) {
-            return false;
-        }
     }
     return true;
 }
diff --git a/src/tint/lang/spirv/reader/ast_parser/usage.h b/src/tint/lang/spirv/reader/ast_parser/usage.h
index cf55287..732ad9f 100644
--- a/src/tint/lang/spirv/reader/ast_parser/usage.h
+++ b/src/tint/lang/spirv/reader/ast_parser/usage.h
@@ -66,9 +66,11 @@
     /// @returns true if this usage is a dpeth texture usage.
     bool IsDepthTexture() const { return is_depth_; }
     /// @returns true if this usage is a read-only storage texture
-    bool IsStorageReadTexture() const { return is_storage_read_; }
+    bool IsStorageReadOnlyTexture() const { return is_storage_read_ && !is_storage_write_; }
+    /// @returns true if this usage is a read-write storage texture
+    bool IsStorageReadWriteTexture() const { return is_storage_read_ && is_storage_write_; }
     /// @returns true if this usage is a write-only storage texture
-    bool IsStorageWriteTexture() const { return is_storage_write_; }
+    bool IsStorageWriteOnlyTexture() const { return is_storage_write_ && !is_storage_read_; }
 
     /// @returns true if this is a storage texture.
     bool IsStorageTexture() const { return is_storage_read_ || is_storage_write_; }
diff --git a/src/tint/lang/spirv/reader/ast_parser/usage_test.cc b/src/tint/lang/spirv/reader/ast_parser/usage_test.cc
index fb6dee1..08c06dd 100644
--- a/src/tint/lang/spirv/reader/ast_parser/usage_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/usage_test.cc
@@ -34,8 +34,9 @@
     EXPECT_FALSE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 }
 
 TEST_F(SpirvASTParserTest, Usage_Trivial_Output) {
@@ -87,8 +88,9 @@
     EXPECT_FALSE(a.IsSampledTexture());
     EXPECT_FALSE(a.IsMultisampledTexture());
     EXPECT_FALSE(a.IsDepthTexture());
-    EXPECT_TRUE(a.IsStorageReadTexture());
-    EXPECT_FALSE(a.IsStorageWriteTexture());
+    EXPECT_TRUE(a.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(a.IsStorageReadWriteTexture());
+    EXPECT_FALSE(a.IsStorageWriteOnlyTexture());
 
     StringStream ss;
     ss << a;
@@ -108,8 +110,9 @@
     EXPECT_FALSE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Sampler( ))"));
@@ -133,8 +136,9 @@
     EXPECT_FALSE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Sampler( comparison ))"));
@@ -157,8 +161,9 @@
     EXPECT_FALSE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Texture( ))"));
@@ -181,8 +186,9 @@
     EXPECT_TRUE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ))"));
@@ -205,8 +211,9 @@
     EXPECT_TRUE(u.IsSampledTexture());
     EXPECT_TRUE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled ms ))"));
@@ -229,8 +236,9 @@
     EXPECT_TRUE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_TRUE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Texture( is_sampled depth ))"));
@@ -253,8 +261,9 @@
     EXPECT_FALSE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_TRUE(u.IsStorageReadTexture());
-    EXPECT_FALSE(u.IsStorageWriteTexture());
+    EXPECT_TRUE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Texture( read ))"));
@@ -277,8 +286,9 @@
     EXPECT_FALSE(u.IsSampledTexture());
     EXPECT_FALSE(u.IsMultisampledTexture());
     EXPECT_FALSE(u.IsDepthTexture());
-    EXPECT_FALSE(u.IsStorageReadTexture());
-    EXPECT_TRUE(u.IsStorageWriteTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_FALSE(u.IsStorageReadWriteTexture());
+    EXPECT_TRUE(u.IsStorageWriteOnlyTexture());
 
     ss << u;
     EXPECT_THAT(ss.str(), Eq("Usage(Texture( write ))"));
@@ -288,5 +298,32 @@
     EXPECT_TRUE(u == copy);
 }
 
+TEST_F(SpirvASTParserTest, Usage_AddStorageReadWriteTexture) {
+    StringStream ss;
+    Usage u;
+    u.AddStorageReadTexture();
+    u.AddStorageWriteTexture();
+
+    EXPECT_TRUE(u.IsValid());
+    EXPECT_TRUE(u.IsComplete());
+    EXPECT_FALSE(u.IsSampler());
+    EXPECT_FALSE(u.IsComparisonSampler());
+    EXPECT_TRUE(u.IsTexture());
+    EXPECT_FALSE(u.IsSampledTexture());
+    EXPECT_FALSE(u.IsMultisampledTexture());
+    EXPECT_FALSE(u.IsDepthTexture());
+    EXPECT_FALSE(u.IsStorageReadOnlyTexture());
+    EXPECT_TRUE(u.IsStorageReadWriteTexture());
+    EXPECT_FALSE(u.IsStorageWriteOnlyTexture());
+
+    ss << u;
+    EXPECT_THAT(ss.str(), Eq("Usage(Texture( read write ))"));
+
+    auto copy(u);
+    u.AddStorageReadTexture();
+    u.AddStorageWriteTexture();
+    EXPECT_TRUE(u == copy);
+}
+
 }  // namespace
 }  // namespace tint::spirv::reader::ast_parser
diff --git a/src/tint/lang/spirv/reader/common/options.h b/src/tint/lang/spirv/reader/common/options.h
index 6dddb44..38b9518 100644
--- a/src/tint/lang/spirv/reader/common/options.h
+++ b/src/tint/lang/spirv/reader/common/options.h
@@ -21,6 +21,8 @@
 struct Options {
     /// Set to `true` to allow calls to derivative builtins in non-uniform control flow.
     bool allow_non_uniform_derivatives = false;
+    /// Set to `true` to allow use of Chromium-specific extensions.
+    bool allow_chromium_extensions = false;
 };
 
 }  // namespace tint::spirv::reader