validation: Validate invariant attribute

This is only valid on structure members and entry point return
types. Additionally, this can only be applied to a position builtin.

Bug: tint:772
Change-Id: Iffd774ca768ab66373678ecf0eb57c766767e92b
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57641
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/program_builder.h b/src/program_builder.h
index dcf311c..f0b0e05 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -39,6 +39,7 @@
 #include "src/ast/i32.h"
 #include "src/ast/if_statement.h"
 #include "src/ast/interpolate_decoration.h"
+#include "src/ast/invariant_decoration.h"
 #include "src/ast/loop_statement.h"
 #include "src/ast/matrix.h"
 #include "src/ast/member_accessor_expression.h"
@@ -2042,6 +2043,19 @@
     return create<ast::InterpolateDecoration>(source_, type, sampling);
   }
 
+  /// Creates an ast::InvariantDecoration
+  /// @param source the source information
+  /// @returns the invariant decoration pointer
+  ast::InvariantDecoration* Invariant(const Source& source) {
+    return create<ast::InvariantDecoration>(source);
+  }
+
+  /// Creates an ast::InvariantDecoration
+  /// @returns the invariant decoration pointer
+  ast::InvariantDecoration* Invariant() {
+    return create<ast::InvariantDecoration>(source_);
+  }
+
   /// Creates an ast::LocationDecoration
   /// @param source the source information
   /// @param location the location value
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 61f8d36..6b893ac 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -62,6 +62,7 @@
   kBuiltin,
   kGroup,
   kInterpolate,
+  kInvariant,
   kLocation,
   kOverride,
   kOffset,
@@ -107,6 +108,8 @@
       return {builder.Interpolate(source, ast::InterpolationType::kLinear,
                                   ast::InterpolationSampling::kCenter),
               builder.Location(0)};
+    case DecorationKind::kInvariant:
+      return {builder.Invariant(source)};
     case DecorationKind::kLocation:
       return {builder.Location(source, 1)};
     case DecorationKind::kOverride:
@@ -157,6 +160,7 @@
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, false},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, false},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
@@ -193,6 +197,7 @@
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, true},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, true},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
@@ -257,6 +262,7 @@
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, false},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, false},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
@@ -293,6 +299,7 @@
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, true},
+                    // kInvariant tested separately (requires position builtin)
                     TestParams{DecorationKind::kLocation, true},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
@@ -317,6 +324,32 @@
 12:34 note: first decoration declared here)");
 }
 
+TEST_F(EntryPointReturnTypeDecorationTest, InvariantWithPosition) {
+  Func("main", ast::VariableList{}, ty.vec4<f32>(),
+       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+       ast::DecorationList{Stage(ast::PipelineStage::kVertex)},
+       ast::DecorationList{
+           Invariant(Source{{12, 34}}),
+           Builtin(Source{{56, 78}}, ast::Builtin::kPosition),
+       });
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(EntryPointReturnTypeDecorationTest, InvariantWithoutPosition) {
+  Func("main", ast::VariableList{}, ty.vec4<f32>(),
+       ast::StatementList{Return(Construct(ty.vec4<f32>()))},
+       ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
+       ast::DecorationList{
+           Invariant(Source{{12, 34}}),
+           Location(Source{{56, 78}}, 0),
+       });
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: invariant attribute must only be applied to a "
+            "position builtin");
+}
+
 using ArrayDecorationTest = TestWithParams;
 TEST_P(ArrayDecorationTest, IsValid) {
   auto& params = GetParam();
@@ -347,6 +380,7 @@
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, false},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, false},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
@@ -382,6 +416,7 @@
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, false},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, false},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
@@ -445,6 +480,7 @@
                     TestParams{DecorationKind::kBuiltin, true},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, true},
+                    TestParams{DecorationKind::kInvariant, true},
                     TestParams{DecorationKind::kLocation, true},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, true},
@@ -507,6 +543,7 @@
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, false},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, false},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
@@ -558,6 +595,7 @@
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, false},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, false},
                     TestParams{DecorationKind::kOverride, true},
                     TestParams{DecorationKind::kOffset, false},
@@ -607,6 +645,7 @@
                     TestParams{DecorationKind::kBuiltin, false},
                     TestParams{DecorationKind::kGroup, false},
                     TestParams{DecorationKind::kInterpolate, false},
+                    TestParams{DecorationKind::kInvariant, false},
                     TestParams{DecorationKind::kLocation, false},
                     TestParams{DecorationKind::kOverride, false},
                     TestParams{DecorationKind::kOffset, false},
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 0c48f04..fe559f3 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -722,7 +722,7 @@
     } else {
       bool is_shader_io_decoration =
           deco->IsAnyOf<ast::BuiltinDecoration, ast::InterpolateDecoration,
-                        ast::LocationDecoration>();
+                        ast::InvariantDecoration, ast::LocationDecoration>();
       bool has_io_storage_class =
           info->storage_class == ast::StorageClass::kInput ||
           info->storage_class == ast::StorageClass::kOutput;
@@ -1194,6 +1194,7 @@
           return false;
         }
       } else if (!deco->IsAnyOf<ast::LocationDecoration, ast::BuiltinDecoration,
+                                ast::InvariantDecoration,
                                 ast::InternalDecoration>() &&
                  (IsValidationEnabled(
                       info->declaration->decorations(),
@@ -1242,6 +1243,7 @@
     // Scan decorations for pipeline IO attributes.
     // Check for overlap with attributes that have been seen previously.
     ast::Decoration* pipeline_io_attribute = nullptr;
+    ast::InvariantDecoration* invariant_attribute = nullptr;
     for (auto* deco : decos) {
       if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
         if (pipeline_io_attribute) {
@@ -1286,6 +1288,8 @@
           return false;
         }
         locations.emplace(location->value());
+      } else if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
+        invariant_attribute = invariant;
       }
     }
 
@@ -1312,10 +1316,19 @@
         return false;
       }
 
+      auto* builtin = pipeline_io_attribute->As<ast::BuiltinDecoration>();
+      if (invariant_attribute &&
+          !(builtin && builtin->value() == ast::Builtin::kPosition)) {
+        AddError(
+            "invariant attribute must only be applied to a position builtin",
+            invariant_attribute->source());
+        return false;
+      }
+
       // Check that all user defined attributes are numeric scalars, vectors
       // of numeric scalars.
       // Testing for being a struct is handled by the if portion above.
-      if (!pipeline_io_attribute->Is<ast::BuiltinDecoration>()) {
+      if (!builtin) {
         if (!ty->is_numeric_scalar_or_vector()) {
           AddError(
               "User defined entry point IO types must be a numeric scalar, "
@@ -3558,6 +3571,7 @@
     for (auto* deco : member->Declaration()->decorations()) {
       if (!(deco->Is<ast::BuiltinDecoration>() ||
             deco->Is<ast::InterpolateDecoration>() ||
+            deco->Is<ast::InvariantDecoration>() ||
             deco->Is<ast::LocationDecoration>() ||
             deco->Is<ast::StructMemberOffsetDecoration>() ||
             deco->Is<ast::StructMemberSizeDecoration>() ||