writer/spirv: Implement invariant attribute

Bug: tint:772
Change-Id: I94f8e95f7c1206f33dbf4defba2d22d95a5cfb1e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57643
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/transform/spirv.cc b/src/transform/spirv.cc
index 0d84459..1582db4 100644
--- a/src/transform/spirv.cc
+++ b/src/transform/spirv.cc
@@ -145,9 +145,9 @@
       for (auto* member : struct_ty->members()) {
         ast::DecorationList new_decorations = RemoveDecorations(
             &ctx, member->decorations(), [](const ast::Decoration* deco) {
-              return deco
-                  ->IsAnyOf<ast::BuiltinDecoration, ast::InterpolateDecoration,
-                            ast::LocationDecoration>();
+              return deco->IsAnyOf<
+                  ast::BuiltinDecoration, ast::InterpolateDecoration,
+                  ast::InvariantDecoration, ast::LocationDecoration>();
             });
         new_struct_members.push_back(
             ctx.dst->Member(ctx.Clone(member->symbol()),
@@ -316,9 +316,9 @@
     // Base case: create a global variable and return.
     ast::DecorationList new_decorations =
         RemoveDecorations(&ctx, decorations, [](const ast::Decoration* deco) {
-          return !deco->IsAnyOf<ast::BuiltinDecoration,
-                                ast::InterpolateDecoration,
-                                ast::LocationDecoration>();
+          return !deco->IsAnyOf<
+              ast::BuiltinDecoration, ast::InterpolateDecoration,
+              ast::InvariantDecoration, ast::LocationDecoration>();
         });
     new_decorations.push_back(
         ctx.dst->ASTNodes().Create<ast::DisableValidationDecoration>(
@@ -382,9 +382,9 @@
     // Create a global variable.
     ast::DecorationList new_decorations =
         RemoveDecorations(&ctx, decorations, [](const ast::Decoration* deco) {
-          return !deco->IsAnyOf<ast::BuiltinDecoration,
-                                ast::InterpolateDecoration,
-                                ast::LocationDecoration>();
+          return !deco->IsAnyOf<
+              ast::BuiltinDecoration, ast::InterpolateDecoration,
+              ast::InvariantDecoration, ast::LocationDecoration>();
         });
     new_decorations.push_back(
         ctx.dst->ASTNodes().Create<ast::DisableValidationDecoration>(
diff --git a/src/transform/spirv_test.cc b/src/transform/spirv_test.cc
index f897004..6edfb24 100644
--- a/src/transform/spirv_test.cc
+++ b/src/transform/spirv_test.cc
@@ -601,6 +601,58 @@
   EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(SpirvTest, HandleEntryPointIOTypes_InvariantAttributes) {
+  auto* src = R"(
+struct VertexOut {
+  [[builtin(position), invariant]] pos : vec4<f32>;
+};
+
+[[stage(vertex)]]
+fn main1() -> VertexOut {
+  return VertexOut();
+}
+
+[[stage(vertex)]]
+fn main2() -> [[builtin(position), invariant]] vec4<f32> {
+  return vec4<f32>();
+}
+)";
+
+  auto* expect = R"(
+struct VertexOut {
+  pos : vec4<f32>;
+};
+
+[[builtin(position), invariant, internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_1 : vec4<f32>;
+
+fn tint_symbol_2(tint_symbol : VertexOut) {
+  tint_symbol_1 = tint_symbol.pos;
+}
+
+[[stage(vertex)]]
+fn main1() {
+  tint_symbol_2(VertexOut());
+  return;
+}
+
+[[builtin(position), invariant, internal(disable_validation__ignore_storage_class)]] var<out> tint_symbol_4 : vec4<f32>;
+
+fn tint_symbol_5(tint_symbol_3 : vec4<f32>) {
+  tint_symbol_4 = tint_symbol_3;
+}
+
+[[stage(vertex)]]
+fn main2() {
+  tint_symbol_5(vec4<f32>());
+  return;
+}
+)";
+
+  auto got = Run<Spirv>(src);
+
+  EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(SpirvTest, HandleEntryPointIOTypes_StructLayoutDecorations) {
   auto* src = R"(
 [[block]]
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index aca145a..9382362 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -853,6 +853,9 @@
     } else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
       AddInterpolationDecorations(var_id, interpolate->type(),
                                   interpolate->sampling());
+    } else if (deco->Is<ast::InvariantDecoration>()) {
+      push_annot(spv::Op::OpDecorate,
+                 {Operand::Int(var_id), Operand::Int(SpvDecorationInvariant)});
     } else if (auto* binding = deco->As<ast::BindingDecoration>()) {
       push_annot(spv::Op::OpDecorate,
                  {Operand::Int(var_id), Operand::Int(SpvDecorationBinding),
diff --git a/test/shader_io/invariant.wgsl.expected.spvasm b/test/shader_io/invariant.wgsl.expected.spvasm
index 93feddc..7759407 100644
--- a/test/shader_io/invariant.wgsl.expected.spvasm
+++ b/test/shader_io/invariant.wgsl.expected.spvasm
@@ -1,9 +1,40 @@
-SKIP: FAILED
-
-
-[[stage(vertex)]]
-fn main() -> [[builtin(position), invariant]] vec4<f32> {
-  return vec4<f32>();
-}
-
-Failed to generate: unknown decoration
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 19
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %tint_pointsize %tint_symbol_1
+               OpName %tint_pointsize "tint_pointsize"
+               OpName %tint_symbol_1 "tint_symbol_1"
+               OpName %tint_symbol_2 "tint_symbol_2"
+               OpName %tint_symbol "tint_symbol"
+               OpName %main "main"
+               OpDecorate %tint_pointsize BuiltIn PointSize
+               OpDecorate %tint_symbol_1 BuiltIn Position
+               OpDecorate %tint_symbol_1 Invariant
+      %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+          %4 = OpConstantNull %float
+%tint_pointsize = OpVariable %_ptr_Output_float Output %4
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+          %8 = OpConstantNull %v4float
+%tint_symbol_1 = OpVariable %_ptr_Output_v4float Output %8
+       %void = OpTypeVoid
+          %9 = OpTypeFunction %void %v4float
+         %14 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+%tint_symbol_2 = OpFunction %void None %9
+%tint_symbol = OpFunctionParameter %v4float
+         %13 = OpLabel
+               OpStore %tint_symbol_1 %tint_symbol
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %14
+         %16 = OpLabel
+               OpStore %tint_pointsize %float_1
+         %18 = OpFunctionCall %void %tint_symbol_2 %8
+               OpReturn
+               OpFunctionEnd
diff --git a/test/shader_io/invariant_struct_member.wgsl.expected.spvasm b/test/shader_io/invariant_struct_member.wgsl.expected.spvasm
index 08c28fc..b062a96 100644
--- a/test/shader_io/invariant_struct_member.wgsl.expected.spvasm
+++ b/test/shader_io/invariant_struct_member.wgsl.expected.spvasm
@@ -1,14 +1,46 @@
-SKIP: FAILED
-
-
-struct Out {
-  [[builtin(position), invariant]]
-  pos : vec4<f32>;
-};
-
-[[stage(vertex)]]
-fn main() -> Out {
-  return Out();
-}
-
-Failed to generate: unknown decoration
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 22
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main" %tint_pointsize %tint_symbol_1
+               OpName %tint_pointsize "tint_pointsize"
+               OpName %tint_symbol_1 "tint_symbol_1"
+               OpName %Out "Out"
+               OpMemberName %Out 0 "pos"
+               OpName %tint_symbol_2 "tint_symbol_2"
+               OpName %tint_symbol "tint_symbol"
+               OpName %main "main"
+               OpDecorate %tint_pointsize BuiltIn PointSize
+               OpDecorate %tint_symbol_1 BuiltIn Position
+               OpDecorate %tint_symbol_1 Invariant
+               OpMemberDecorate %Out 0 Offset 0
+      %float = OpTypeFloat 32
+%_ptr_Output_float = OpTypePointer Output %float
+          %4 = OpConstantNull %float
+%tint_pointsize = OpVariable %_ptr_Output_float Output %4
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+          %8 = OpConstantNull %v4float
+%tint_symbol_1 = OpVariable %_ptr_Output_v4float Output %8
+       %void = OpTypeVoid
+        %Out = OpTypeStruct %v4float
+          %9 = OpTypeFunction %void %Out
+         %16 = OpTypeFunction %void
+    %float_1 = OpConstant %float 1
+         %21 = OpConstantNull %Out
+%tint_symbol_2 = OpFunction %void None %9
+%tint_symbol = OpFunctionParameter %Out
+         %14 = OpLabel
+         %15 = OpCompositeExtract %v4float %tint_symbol 0
+               OpStore %tint_symbol_1 %15
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %16
+         %18 = OpLabel
+               OpStore %tint_pointsize %float_1
+         %20 = OpFunctionCall %void %tint_symbol_2 %21
+               OpReturn
+               OpFunctionEnd