[ir] Move LoosenValidationForShaderIO to a property

The SPIR-V parser and the ShaderIO transforms in `raise` add the
property, and the SPIR-V ShaderIO transform in `lower` removes it.

The WGSL writer rejects it.

Bug: 512904070
Change-Id: Ie6a8894f7d8a6ba5a363fcf6fc7339aad721aac4
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/320017
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/capabilities.h b/src/tint/lang/core/ir/capabilities.h
index d7b1781..e83f7d2a 100644
--- a/src/tint/lang/core/ir/capabilities.h
+++ b/src/tint/lang/core/ir/capabilities.h
@@ -37,11 +37,6 @@
 enum class Capability : uint8_t {
     /// Allows 64-bit integer types.
     kAllow64BitIntegers,
-    /// Allows ShaderIO specific features, like blend_src on non-struct members.
-    /// These are not separate capabilities, because they are enabled/disabled in lockstep with each
-    /// other.
-    /// TODO(448417342): Validate in/out address space usage based on this capability
-    kLoosenValidationForShaderIO,
 };
 
 /// Capabilities is a set of Capability
diff --git a/src/tint/lang/core/ir/module.h b/src/tint/lang/core/ir/module.h
index dcfbc6c..f5945ec 100644
--- a/src/tint/lang/core/ir/module.h
+++ b/src/tint/lang/core/ir/module.h
@@ -64,6 +64,8 @@
     kAllowAnyInputAttachmentIndexType,
     /// Allows lets to have any type.
     kAllowAnyLetType,
+    /// Allows various backend-specific features for ShaderIO, like blend_src on non-struct members.
+    kAllowBackendSpecificShaderIO,
     /// Allows ClipDistances on f32 and vecN<f32> parameters
     kAllowClipDistancesOnF32ScalarAndVector,
     /// Allows binding points to be non-unique.
@@ -119,6 +121,7 @@
         CASE(Allow16BitIntegers);
         CASE(AllowAnyInputAttachmentIndexType);
         CASE(AllowAnyLetType);
+        CASE(AllowBackendSpecificShaderIO);
         CASE(AllowClipDistancesOnF32ScalarAndVector);
         CASE(AllowDuplicateBindings);
         CASE(AllowLocationForNumericComposites);
diff --git a/src/tint/lang/core/ir/structural_validator.cc b/src/tint/lang/core/ir/structural_validator.cc
index bd9dec8..9871f5f 100644
--- a/src/tint/lang/core/ir/structural_validator.cc
+++ b/src/tint/lang/core/ir/structural_validator.cc
@@ -1503,7 +1503,7 @@
                                           Function::PipelineStage stage,
                                           IODirection dir,
                                           ShaderIOKind io_kind) {
-    bool skip_builtins = capabilities_.Contains(Capability::kLoosenValidationForShaderIO) &&
+    bool skip_builtins = ir_.properties.Contains(Property::kAllowBackendSpecificShaderIO) &&
                          io_kind == ShaderIOKind::kModuleScopeVar;
     const IOAttributeUsage usage = IOAttributeUsageFor(stage, dir);
     WalkTypeAndMembers(
@@ -2088,7 +2088,7 @@
                                const core::type::Type* ty,
                                const IOAttributes& attr) {
     if (attr.blend_src.has_value()) {
-        if (!capabilities_.Contains(Capability::kLoosenValidationForShaderIO)) {
+        if (!ir_.properties.Contains(Property::kAllowBackendSpecificShaderIO)) {
             AddError(target) << "blend_src cannot be used on non-struct-member types";
         }
         CheckBlendSrcImpl(ctx, target, ty, attr);
@@ -2115,7 +2115,7 @@
     }
 
     // Reject blend_src on nested members
-    if (!capabilities_.Contains(Capability::kLoosenValidationForShaderIO)) {
+    if (!ir_.properties.Contains(Property::kAllowBackendSpecificShaderIO)) {
         WalkTypeAndMembers(
             ctx, ty, attr,
             [&target, this](BlendSrcContext&, const core::type::Type*, const IOAttributes& a) {
@@ -2237,7 +2237,7 @@
             }
 
             if (a.interpolation.has_value()) {
-                has_location |= (capabilities_.Contains(Capability::kLoosenValidationForShaderIO) &&
+                has_location |= (ir_.properties.Contains(Property::kAllowBackendSpecificShaderIO) &&
                                  a.builtin.has_value());
 
                 if (!ir_.properties.Contains(Property::kAllowLocationForNumericComposites) &&
@@ -2284,7 +2284,7 @@
                 }
 
                 if (!has_location) {
-                    if (!capabilities_.Contains(Capability::kLoosenValidationForShaderIO)) {
+                    if (!ir_.properties.Contains(Property::kAllowBackendSpecificShaderIO)) {
                         AddError(anchor) << "interpolation attribute requires a location attribute";
                     } else {
                         AddError(anchor) << "interpolation attribute requires a location attribute "
diff --git a/src/tint/lang/core/ir/transform/dead_code_elimination.cc b/src/tint/lang/core/ir/transform/dead_code_elimination.cc
index 0292006..541ad00 100644
--- a/src/tint/lang/core/ir/transform/dead_code_elimination.cc
+++ b/src/tint/lang/core/ir/transform/dead_code_elimination.cc
@@ -105,7 +105,7 @@
 }  // namespace
 
 Result<SuccessType> DeadCodeElimination(Module& ir) {
-    core::ir::AssertValid(ir, kDeadCodeEliminationCapabilities, "before core.DeadCodeElimination");
+    core::ir::AssertValid(ir, "before core.DeadCodeElimination");
 
     State{ir}.Process();
 
diff --git a/src/tint/lang/core/ir/transform/dead_code_elimination.h b/src/tint/lang/core/ir/transform/dead_code_elimination.h
index 3f4b61f..2f71296 100644
--- a/src/tint/lang/core/ir/transform/dead_code_elimination.h
+++ b/src/tint/lang/core/ir/transform/dead_code_elimination.h
@@ -28,7 +28,6 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_DEAD_CODE_ELIMINATION_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_DEAD_CODE_ELIMINATION_H_
 
-#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -38,11 +37,6 @@
 
 namespace tint::core::ir::transform {
 
-/// The capabilities that the transform can support.
-const core::ir::Capabilities kDeadCodeEliminationCapabilities{
-    core::ir::Capability::kLoosenValidationForShaderIO,
-};
-
 /// DeadCodeElimination is a transform that removes dead code from the given IR module.
 ///
 /// Currently the eliminator will try to remove:
diff --git a/src/tint/lang/core/ir/transform/dead_code_elimination_fuzz.cc b/src/tint/lang/core/ir/transform/dead_code_elimination_fuzz.cc
index 02d513d..a8696aa 100644
--- a/src/tint/lang/core/ir/transform/dead_code_elimination_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/dead_code_elimination_fuzz.cc
@@ -39,5 +39,4 @@
 }  // namespace
 }  // namespace tint::core::ir::transform
 
-TINT_IR_MODULE_FUZZER(tint::core::ir::transform::DeadCodeEliminationFuzzer,
-                      tint::core::ir::transform::kDeadCodeEliminationCapabilities);
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::DeadCodeEliminationFuzzer, {});
diff --git a/src/tint/lang/core/ir/transform/decompose_access.cc b/src/tint/lang/core/ir/transform/decompose_access.cc
index 503c7e4..dedbd43 100644
--- a/src/tint/lang/core/ir/transform/decompose_access.cc
+++ b/src/tint/lang/core/ir/transform/decompose_access.cc
@@ -1544,11 +1544,7 @@
 }  // namespace
 
 Result<SuccessType> DecomposeAccess(core::ir::Module& ir, const DecomposeAccessOptions& options) {
-    core::ir::AssertValid(ir,
-                          core::ir::Capabilities{
-                              core::ir::Capability::kLoosenValidationForShaderIO,
-                          },
-                          "before core.DecomposeAccess");
+    core::ir::AssertValid(ir, "before core.DecomposeAccess");
 
     auto res = State{ir, options}.Process();
     if (res != Success) {
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args.h b/src/tint/lang/core/ir/transform/remove_terminator_args.h
index 4206414..979f7da 100644
--- a/src/tint/lang/core/ir/transform/remove_terminator_args.h
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args.h
@@ -41,7 +41,6 @@
 /// The capabilities that the transform can support.
 const core::ir::Capabilities kRemoveTerminatorArgsCapabilities{
     core::ir::Capability::kAllow64BitIntegers,
-    core::ir::Capability::kLoosenValidationForShaderIO,
 };
 
 /// RemoveTerminatorArgs is a transform that removes all arguments from terminator instructions and
diff --git a/src/tint/lang/core/ir/transform/rename_conflicts.h b/src/tint/lang/core/ir/transform/rename_conflicts.h
index 3e68916..24da3ac 100644
--- a/src/tint/lang/core/ir/transform/rename_conflicts.h
+++ b/src/tint/lang/core/ir/transform/rename_conflicts.h
@@ -41,7 +41,6 @@
 /// The capabilities that the transform can support.
 const core::ir::Capabilities kRenameConflictsCapabilities{
     core::ir::Capability::kAllow64BitIntegers,
-    core::ir::Capability::kLoosenValidationForShaderIO,
 };
 
 /// RenameConflicts is a transform that renames declarations which prevent identifiers from
diff --git a/src/tint/lang/core/ir/transform/std140.cc b/src/tint/lang/core/ir/transform/std140.cc
index 70dbb8d..7d8612f 100644
--- a/src/tint/lang/core/ir/transform/std140.cc
+++ b/src/tint/lang/core/ir/transform/std140.cc
@@ -448,7 +448,7 @@
 }  // namespace
 
 Result<SuccessType> Std140(Module& ir) {
-    AssertValid(ir, kStd140Capabilities, "before core.Std140");
+    AssertValid(ir, "before core.Std140");
 
     State{ir}.Process();
 
diff --git a/src/tint/lang/core/ir/transform/std140.h b/src/tint/lang/core/ir/transform/std140.h
index ffa24d5..2ef073b 100644
--- a/src/tint/lang/core/ir/transform/std140.h
+++ b/src/tint/lang/core/ir/transform/std140.h
@@ -28,7 +28,6 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_STD140_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_STD140_H_
 
-#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -38,11 +37,6 @@
 
 namespace tint::core::ir::transform {
 
-/// The capabilities that the transform can support.
-const core::ir::Capabilities kStd140Capabilities{
-    core::ir::Capability::kLoosenValidationForShaderIO,
-};
-
 /// Std140 is a transform that rewrites matrix types in the uniform address space to conform to
 /// GLSL's std140 layout rules.
 /// @note requires the DirectVariableAccess transform to have been run first to remove uniform
diff --git a/src/tint/lang/core/ir/transform/std140_fuzz.cc b/src/tint/lang/core/ir/transform/std140_fuzz.cc
index 291dec9..ad92e3a 100644
--- a/src/tint/lang/core/ir/transform/std140_fuzz.cc
+++ b/src/tint/lang/core/ir/transform/std140_fuzz.cc
@@ -58,5 +58,4 @@
 }  // namespace
 }  // namespace tint::core::ir::transform
 
-TINT_IR_MODULE_FUZZER(tint::core::ir::transform::Std140Fuzzer,
-                      tint::core::ir::transform::kStd140Capabilities);
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::Std140Fuzzer, {});
diff --git a/src/tint/lang/core/ir/transform/value_to_let.h b/src/tint/lang/core/ir/transform/value_to_let.h
index 94fefd9..17a64b8 100644
--- a/src/tint/lang/core/ir/transform/value_to_let.h
+++ b/src/tint/lang/core/ir/transform/value_to_let.h
@@ -42,7 +42,6 @@
 /// The capabilities that the transform can support.
 const core::ir::Capabilities kValueToLetCapabilities{
     core::ir::Capability::kAllow64BitIntegers,
-    core::ir::Capability::kLoosenValidationForShaderIO,
 };
 
 /// Configuration for ValueToLet transform.
diff --git a/src/tint/lang/core/ir/validator_function_test.cc b/src/tint/lang/core/ir/validator_function_test.cc
index c083b1d..4f8367f 100644
--- a/src/tint/lang/core/ir/validator_function_test.cc
+++ b/src/tint/lang/core/ir/validator_function_test.cc
@@ -1390,9 +1390,8 @@
         b.Store(var1, 1_f);
         b.Return(f);
     });
-    auto res = ir::Validate(mod, Capabilities{
-                                     Capability::kLoosenValidationForShaderIO,
-                                 });
+    mod.properties.Add(ir::Property::kAllowBackendSpecificShaderIO);
+    auto res = ir::Validate(mod);
     ASSERT_EQ(res, Success) << res.Failure();
 }
 
@@ -1441,7 +1440,8 @@
 
     b.Append(f->Block(), [&] { b.Return(f); });
 
-    auto res = ir::Validate(mod, Capabilities{Capability::kLoosenValidationForShaderIO});
+    mod.properties.Add(ir::Property::kAllowBackendSpecificShaderIO);
+    auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_THAT(
         res.Failure().reason,
@@ -1649,7 +1649,8 @@
 
     b.Append(f->Block(), [&] { b.Return(f); });
 
-    auto res = ir::Validate(mod, Capabilities{Capability::kLoosenValidationForShaderIO});
+    mod.properties.Add(ir::Property::kAllowBackendSpecificShaderIO);
+    auto res = ir::Validate(mod);
     ASSERT_EQ(res, Success) << res.Failure();
 }
 
diff --git a/src/tint/lang/glsl/writer/printer/printer.cc b/src/tint/lang/glsl/writer/printer/printer.cc
index ad8ef5e..0844afd 100644
--- a/src/tint/lang/glsl/writer/printer/printer.cc
+++ b/src/tint/lang/glsl/writer/printer/printer.cc
@@ -131,7 +131,7 @@
 
     /// @returns the generated GLSL shader
     tint::Result<Output> Generate() {
-        AssertValid(ir_, kPrinterCapabilities, "before glsl.Printer");
+        AssertValid(ir_, "before glsl.Printer");
         AssertNoUnsupportedProperties(ir_, kUnsupportedProperties);
 
         {
diff --git a/src/tint/lang/glsl/writer/printer/printer.h b/src/tint/lang/glsl/writer/printer/printer.h
index e2094a0..7a9d786 100644
--- a/src/tint/lang/glsl/writer/printer/printer.h
+++ b/src/tint/lang/glsl/writer/printer/printer.h
@@ -28,7 +28,6 @@
 #ifndef SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_
 #define SRC_TINT_LANG_GLSL_WRITER_PRINTER_PRINTER_H_
 
-#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/glsl/writer/common/output.h"
 #include "src/tint/utils/result.h"
 
@@ -42,11 +41,6 @@
 
 namespace tint::glsl::writer {
 
-// The capabilities that might be needed due to raising.
-const core::ir::Capabilities kPrinterCapabilities{
-    core::ir::Capability::kLoosenValidationForShaderIO,
-};
-
 /// @returns the generated GLSL shader on success, or failure
 /// @param module the Tint IR module to generate
 /// @param options the options to use
diff --git a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
index c54b1f3..8cf9827 100644
--- a/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
+++ b/src/tint/lang/glsl/writer/raise/bitcast_polyfill.cc
@@ -313,8 +313,7 @@
 }  // namespace
 
 Result<SuccessType> BitcastPolyfill(core::ir::Module& ir) {
-    AssertValid(ir, core::ir::Capabilities{core::ir::Capability::kLoosenValidationForShaderIO},
-                "before glsl.BitcastPolyfill");
+    AssertValid(ir, "before glsl.BitcastPolyfill");
 
     State{ir}.Process();
 
diff --git a/src/tint/lang/glsl/writer/raise/offset_first_index.cc b/src/tint/lang/glsl/writer/raise/offset_first_index.cc
index 1988ea3..d574935 100644
--- a/src/tint/lang/glsl/writer/raise/offset_first_index.cc
+++ b/src/tint/lang/glsl/writer/raise/offset_first_index.cc
@@ -112,11 +112,7 @@
 }  // namespace
 
 Result<SuccessType> OffsetFirstIndex(core::ir::Module& ir, const OffsetFirstIndexConfig& config) {
-    AssertValid(ir,
-                core::ir::Capabilities{
-                    core::ir::Capability::kLoosenValidationForShaderIO,
-                },
-                "before glsl.OffsetFirstIndex");
+    AssertValid(ir, "before glsl.OffsetFirstIndex");
 
     State{config, ir}.Process();
 
diff --git a/src/tint/lang/glsl/writer/raise/shader_io.cc b/src/tint/lang/glsl/writer/raise/shader_io.cc
index 901c1fb..c6f618b 100644
--- a/src/tint/lang/glsl/writer/raise/shader_io.cc
+++ b/src/tint/lang/glsl/writer/raise/shader_io.cc
@@ -318,6 +318,8 @@
             return std::make_unique<StateImpl>(mod, func, config);
         }));
 
+    ir.properties.Add(core::ir::Property::kAllowBackendSpecificShaderIO);
+
     return Success;
 }
 
diff --git a/src/tint/lang/glsl/writer/raise/shader_io_test.cc b/src/tint/lang/glsl/writer/raise/shader_io_test.cc
index 93aa10f..df4b2d9 100644
--- a/src/tint/lang/glsl/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/glsl/writer/raise/shader_io_test.cc
@@ -41,7 +41,7 @@
 class GlslWriter_ShaderIOTest : public core::ir::transform::TransformTest {
   public:
     GlslWriter_ShaderIOTest() {
-        capabilities.Add(core::ir::Capability::kLoosenValidationForShaderIO);
+        mod.properties.Add(core::ir::Property::kAllowBackendSpecificShaderIO);
     }
 };
 
diff --git a/src/tint/lang/glsl/writer/writer_fuzz.cc b/src/tint/lang/glsl/writer/writer_fuzz.cc
index 97169c5..3af2621 100644
--- a/src/tint/lang/glsl/writer/writer_fuzz.cc
+++ b/src/tint/lang/glsl/writer/writer_fuzz.cc
@@ -176,6 +176,4 @@
 }  // namespace
 }  // namespace tint::glsl::writer
 
-TINT_IR_MODULE_FUZZER(tint::glsl::writer::IRFuzzer,
-                      tint::core::ir::Capabilities{},
-                      tint::glsl::writer::kPrinterCapabilities);
+TINT_IR_MODULE_FUZZER(tint::glsl::writer::IRFuzzer, {});
diff --git a/src/tint/lang/spirv/reader/lower/shader_io.cc b/src/tint/lang/spirv/reader/lower/shader_io.cc
index 3933e4c..5375d8b 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io.cc
@@ -740,14 +740,11 @@
 }  // namespace
 
 Result<SuccessType> ShaderIO(core::ir::Module& ir) {
-    AssertValid(ir,
-                core::ir::Capabilities{
-                    core::ir::Capability::kLoosenValidationForShaderIO,
-                },
-                "before spirv.ShaderIO");
+    AssertValid(ir, "before spirv.ShaderIO");
 
     TINT_CHECK_RESULT(State{ir}.Process());
 
+    ir.properties.Remove(core::ir::Property::kAllowBackendSpecificShaderIO);
     ir.properties.Remove(core::ir::Property::kAllowLocationForNumericComposites);
     ir.properties.Remove(core::ir::Property::kAllowPointSizeBuiltin);
 
diff --git a/src/tint/lang/spirv/reader/lower/shader_io_test.cc b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
index 244bea9..519bc6b 100644
--- a/src/tint/lang/spirv/reader/lower/shader_io_test.cc
+++ b/src/tint/lang/spirv/reader/lower/shader_io_test.cc
@@ -43,6 +43,7 @@
         mod.properties.Add(core::ir::Property::kAllowLocationForNumericComposites);
         mod.properties.Add(core::ir::Property::kAllowMultipleEntryPoints);
         mod.properties.Add(core::ir::Property::kAllowPointSizeBuiltin);
+        mod.properties.Add(core::ir::Property::kAllowBackendSpecificShaderIO);
     }
 
   protected:
diff --git a/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc b/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc
index e472c71..b665424 100644
--- a/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc
+++ b/src/tint/lang/spirv/reader/lower/vector_element_pointer.cc
@@ -154,11 +154,7 @@
 }  // namespace
 
 Result<SuccessType> VectorElementPointer(core::ir::Module& ir) {
-    core::ir::AssertValid(ir,
-                          core::ir::Capabilities{
-                              core::ir::Capability::kLoosenValidationForShaderIO,
-                          },
-                          "before spirv.VectorElementPointer");
+    core::ir::AssertValid(ir, "before spirv.VectorElementPointer");
 
     State{ir}.Process();
 
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 2bb9ca3..ec2ec2b 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -195,6 +195,7 @@
         ir_.properties.Add(core::ir::Property::kAllowStructMatrixDecorations);
         ir_.properties.Add(core::ir::Property::kAllowUnannotatedModuleIOVariables);
         ir_.properties.Add(core::ir::Property::kAllowVectorElementPointer);
+        ir_.properties.Add(core::ir::Property::kAllowBackendSpecificShaderIO);
 
         RegisterNames();
 
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index 207a6f2..0654ce5 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -300,7 +300,7 @@
 
     /// Builds the SPIR-V from the IR
     Result<SuccessType> Generate() {
-        AssertValid(ir_, kPrinterCapabilities, "before spirv.Printer");
+        AssertValid(ir_, "before spirv.Printer");
         AssertNoUnsupportedProperties(ir_, kUnsupportedProperties);
 
         module_.PushCapability(SpvCapabilityShader);
diff --git a/src/tint/lang/spirv/writer/printer/printer.h b/src/tint/lang/spirv/writer/printer/printer.h
index 437a501..8c6cbfc 100644
--- a/src/tint/lang/spirv/writer/printer/printer.h
+++ b/src/tint/lang/spirv/writer/printer/printer.h
@@ -28,7 +28,6 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_PRINTER_PRINTER_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_PRINTER_PRINTER_H_
 
-#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/spirv/writer/common/options.h"
 #include "src/tint/lang/spirv/writer/common/output.h"
 #include "src/tint/utils/result.h"
@@ -40,11 +39,6 @@
 
 namespace tint::spirv::writer {
 
-// The capabilities that might be needed due to raising.
-const core::ir::Capabilities kPrinterCapabilities{
-    core::ir::Capability::kLoosenValidationForShaderIO,
-};
-
 /// @returns the generated SPIR-V instructions on success, or failure
 /// @param module the Tint IR module to generate
 /// @param options the printer options
diff --git a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc
index 0cb1d77..33f875f 100644
--- a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc
+++ b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.cc
@@ -370,7 +370,7 @@
 }  // namespace
 
 Result<SuccessType> ForkExplicitLayoutTypes(core::ir::Module& ir, SpvVersion version) {
-    AssertValid(ir, kForkExplicitLayoutTypesCapabilities, "before spirv.ForkExplicitLayoutTypes");
+    AssertValid(ir, "before spirv.ForkExplicitLayoutTypes");
 
     State{ir, version}.Process();
 
diff --git a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h
index cd6a184..f616bd0 100644
--- a/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h
+++ b/src/tint/lang/spirv/writer/raise/fork_explicit_layout_types.h
@@ -28,7 +28,6 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_FORK_EXPLICIT_LAYOUT_TYPES_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_FORK_EXPLICIT_LAYOUT_TYPES_H_
 
-#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -41,11 +40,6 @@
 
 namespace tint::spirv::writer::raise {
 
-/// The capabilities that the transform can support.
-const core::ir::Capabilities kForkExplicitLayoutTypesCapabilities{
-    core::ir::Capability::kLoosenValidationForShaderIO,
-};
-
 /// ForkExplicitLayoutTypes is a transform that forks array and structures types that are shared
 /// between address spaces that require explicit layout in SPIR-V and those that cannot have them.
 ///
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index 3149c44..4a6534c 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -465,13 +465,15 @@
 }  // namespace
 
 Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
-    AssertValid(ir, kShaderIOCapabilities, "before spirv.ShaderIO");
+    AssertValid(ir, "before spirv.ShaderIO");
 
     TINT_CHECK_RESULT(core::ir::transform::RunShaderIOBase(
         ir, [&](core::ir::Module& mod, core::ir::Function* func) {
             return std::make_unique<StateImpl>(mod, func, config);
         }));
 
+    ir.properties.Add(core::ir::Property::kAllowBackendSpecificShaderIO);
+
     return Success;
 }
 
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.h b/src/tint/lang/spirv/writer/raise/shader_io.h
index 6b0d073..5388f26 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.h
+++ b/src/tint/lang/spirv/writer/raise/shader_io.h
@@ -31,7 +31,6 @@
 #include <unordered_map>
 
 #include "src/tint/lang/core/ir/transform/prepare_immediate_data.h"
-#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/spirv/writer/common/options.h"
 #include "src/tint/utils/result.h"
 
@@ -42,11 +41,6 @@
 
 namespace tint::spirv::writer::raise {
 
-/// The capabilities that the transform can support.
-const core::ir::Capabilities kShaderIOCapabilities{
-    core::ir::Capability::kLoosenValidationForShaderIO,
-};
-
 /// ShaderIOConfig describes the set of configuration options for the ShaderIO transform.
 struct ShaderIOConfig {
     /// immediate data layout information
diff --git a/src/tint/lang/spirv/writer/raise/shader_io_test.cc b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
index 0b79059..17b1670 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
@@ -41,7 +41,7 @@
 class SpirvWriter_ShaderIOTest : public core::ir::transform::TransformTest {
   public:
     SpirvWriter_ShaderIOTest() {
-        capabilities.Add(core::ir::Capability::kLoosenValidationForShaderIO);
+        mod.properties.Add(core::ir::Property::kAllowBackendSpecificShaderIO);
         mod.properties.Add(core::ir::Property::kAllowLocationForNumericComposites);
     }
 };
@@ -590,7 +590,7 @@
 }
 
 TEST_F(SpirvWriter_ShaderIOTest, ReturnValue_DualSourceBlending) {
-    capabilities.Add(core::ir::Capability::kLoosenValidationForShaderIO);
+    mod.properties.Add(core::ir::Property::kAllowBackendSpecificShaderIO);
     auto* str_ty = ty.Struct(mod.symbols.New("Output"), {
                                                             {
                                                                 mod.symbols.New("color1"),
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
index 6f8d96c..3015e40 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
@@ -249,7 +249,7 @@
 }  // namespace
 
 Result<SuccessType> VarForDynamicIndex(core::ir::Module& ir) {
-    core::ir::AssertValid(ir, kVarForDynamicIndexCapabilities, "before spirv.VarForDynamicIndex");
+    core::ir::AssertValid(ir, "before spirv.VarForDynamicIndex");
 
     State{ir}.Process();
 
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
index 33559b6..e3e1b8c 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.h
@@ -28,7 +28,6 @@
 #ifndef SRC_TINT_LANG_SPIRV_WRITER_RAISE_VAR_FOR_DYNAMIC_INDEX_H_
 #define SRC_TINT_LANG_SPIRV_WRITER_RAISE_VAR_FOR_DYNAMIC_INDEX_H_
 
-#include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/result.h"
 
 // Forward declarations.
@@ -38,11 +37,6 @@
 
 namespace tint::spirv::writer::raise {
 
-/// The capabilities that the transform can support.
-const core::ir::Capabilities kVarForDynamicIndexCapabilities{
-    core::ir::Capability::kLoosenValidationForShaderIO,
-};
-
 /// VarForDynamicIndex is a transform that copies array and matrix values that are dynamically
 /// indexed to a temporary local `var` before performing the index. This transform is used by the
 /// SPIR-V writer as there is no SPIR-V instruction that can dynamically index a non-pointer
diff --git a/src/tint/lang/spirv/writer/writer_fuzz.cc b/src/tint/lang/spirv/writer/writer_fuzz.cc
index 682d8d1..f35efc0 100644
--- a/src/tint/lang/spirv/writer/writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/writer_fuzz.cc
@@ -387,6 +387,4 @@
 }  // namespace
 }  // namespace tint::spirv::writer
 
-TINT_IR_MODULE_FUZZER(tint::spirv::writer::IRFuzzer,
-                      tint::core::ir::Capabilities{},
-                      tint::spirv::writer::kPrinterCapabilities);
+TINT_IR_MODULE_FUZZER(tint::spirv::writer::IRFuzzer, {});
diff --git a/src/tint/lang/wgsl/writer/writer.cc b/src/tint/lang/wgsl/writer/writer.cc
index 32651b7..9809a9a 100644
--- a/src/tint/lang/wgsl/writer/writer.cc
+++ b/src/tint/lang/wgsl/writer/writer.cc
@@ -60,6 +60,7 @@
     const core::ir::Properties kUnsupportedProperties{
         core::ir::Property::kAllow8BitIntegers,
         core::ir::Property::kAllow16BitIntegers,
+        core::ir::Property::kAllowBackendSpecificShaderIO,
         core::ir::Property::kAllowNonCoreTypes,
         core::ir::Property::kAllowUnannotatedModuleIOVariables,
     };