writer/msl: Implement invariant attribute

Report whether one was generated so that Dawn knows to use the
`-fpreserve-invariance` compiler option.

Bug: tint:772
Change-Id: Ife1eb05265646727dc864f12f983781af4df3777
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/57644
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/transform/canonicalize_entry_point_io.cc b/src/transform/canonicalize_entry_point_io.cc
index e8bc3dc..bd30b0d 100644
--- a/src/transform/canonicalize_entry_point_io.cc
+++ b/src/transform/canonicalize_entry_point_io.cc
@@ -84,9 +84,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()),
@@ -156,9 +156,9 @@
             ast::DecorationList new_decorations = RemoveDecorations(
                 &ctx, member->Declaration()->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>();
                 });
 
             if (cfg->builtin_style == BuiltinStyle::kParameter &&
@@ -246,9 +246,9 @@
           ast::DecorationList new_decorations = RemoveDecorations(
               &ctx, member->Declaration()->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>();
               });
           auto symbol = ctx.Clone(member->Declaration()->symbol());
           auto* member_ty = ctx.Clone(member->Declaration()->type());
diff --git a/src/transform/canonicalize_entry_point_io_test.cc b/src/transform/canonicalize_entry_point_io_test.cc
index d0a59e3..1f12d40 100644
--- a/src/transform/canonicalize_entry_point_io_test.cc
+++ b/src/transform/canonicalize_entry_point_io_test.cc
@@ -691,6 +691,58 @@
   EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(CanonicalizeEntryPointIOTest, 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>;
+};
+
+struct tint_symbol {
+  [[builtin(position), invariant]]
+  pos : vec4<f32>;
+};
+
+[[stage(vertex)]]
+fn main1() -> tint_symbol {
+  let tint_symbol_1 : VertexOut = VertexOut();
+  return tint_symbol(tint_symbol_1.pos);
+}
+
+struct tint_symbol_2 {
+  [[builtin(position), invariant]]
+  value : vec4<f32>;
+};
+
+[[stage(vertex)]]
+fn main2() -> tint_symbol_2 {
+  return tint_symbol_2(vec4<f32>());
+}
+)";
+
+  DataMap data;
+  data.Add<CanonicalizeEntryPointIO::Config>(
+      CanonicalizeEntryPointIO::BuiltinStyle::kStructMember);
+  auto got = Run<CanonicalizeEntryPointIO>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
 TEST_F(CanonicalizeEntryPointIOTest, Struct_LayoutDecorations) {
   auto* src = R"(
 [[block]]
diff --git a/src/writer/msl/generator.cc b/src/writer/msl/generator.cc
index 45287fe..215d650 100644
--- a/src/writer/msl/generator.cc
+++ b/src/writer/msl/generator.cc
@@ -48,6 +48,7 @@
   result.success = impl->Generate();
   result.error = impl->error();
   result.msl = impl->result();
+  result.has_invariant_attribute = impl->HasInvariant();
 
   return result;
 }
diff --git a/src/writer/msl/generator.h b/src/writer/msl/generator.h
index b4891c9..5028458 100644
--- a/src/writer/msl/generator.h
+++ b/src/writer/msl/generator.h
@@ -63,6 +63,9 @@
 
   /// True if the shader needs a UBO of buffer sizes.
   bool needs_storage_buffer_sizes = false;
+
+  /// True if the generated shader uses the invariant attribute.
+  bool has_invariant_attribute = false;
 };
 
 /// Generate MSL for a program, according to a set of configuration options. The
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 96ebba6..05768ae 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -2134,6 +2134,9 @@
           return false;
         }
         out << " [[" << attr << "]]";
+      } else if (deco->Is<ast::InvariantDecoration>()) {
+        out << " [[invariant]]";
+        has_invariant_ = true;
       } else if (!deco->IsAnyOf<ast::StructMemberOffsetDecoration,
                                 ast::StructMemberAlignDecoration,
                                 ast::StructMemberSizeDecoration>()) {
diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h
index 49333e3..fde3c9a 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -61,6 +61,9 @@
   /// @returns true on successful generation; false otherwise
   bool Generate();
 
+  /// @returns true if an invariant attribute was generated
+  bool HasInvariant() { return has_invariant_; }
+
   /// Handles generating a declared type
   /// @param ty the declared type to generate
   /// @returns true if the declared type was emitted
@@ -302,6 +305,9 @@
   /// Name of atomicCompareExchangeWeak() helper for the given pointer storage
   /// class.
   StorageClassToString atomicCompareExchangeWeak_;
+
+  /// True if an invariant attribute has been generated.
+  bool has_invariant_ = false;
 };
 
 }  // namespace msl
diff --git a/src/writer/msl/generator_impl_test.cc b/src/writer/msl/generator_impl_test.cc
index 72f32b0..f7c23e5 100644
--- a/src/writer/msl/generator_impl_test.cc
+++ b/src/writer/msl/generator_impl_test.cc
@@ -88,6 +88,55 @@
                     MslBuiltinData{ast::Builtin::kSampleIndex, "sample_id"},
                     MslBuiltinData{ast::Builtin::kSampleMask, "sample_mask"}));
 
+TEST_F(MslGeneratorImplTest, HasInvariantAttribute_True) {
+  auto* out = Structure(
+      "Out", {Member("pos", ty.vec4<f32>(),
+                     {Builtin(ast::Builtin::kPosition), Invariant()})});
+  Func("vert_main", ast::VariableList{}, ty.Of(out),
+       {Return(Construct(ty.Of(out)))}, {Stage(ast::PipelineStage::kVertex)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_TRUE(gen.HasInvariant());
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Out {
+  float4 pos [[position]] [[invariant]];
+};
+
+vertex Out vert_main() {
+  return {};
+}
+
+)");
+}
+
+TEST_F(MslGeneratorImplTest, HasInvariantAttribute_False) {
+  auto* out = Structure("Out", {Member("pos", ty.vec4<f32>(),
+                                       {Builtin(ast::Builtin::kPosition)})});
+  Func("vert_main", ast::VariableList{}, ty.Of(out),
+       {Return(Construct(ty.Of(out)))}, {Stage(ast::PipelineStage::kVertex)});
+
+  GeneratorImpl& gen = Build();
+
+  ASSERT_TRUE(gen.Generate()) << gen.error();
+  EXPECT_FALSE(gen.HasInvariant());
+  EXPECT_EQ(gen.result(), R"(#include <metal_stdlib>
+
+using namespace metal;
+struct Out {
+  float4 pos [[position]];
+};
+
+vertex Out vert_main() {
+  return {};
+}
+
+)");
+}
+
 }  // namespace
 }  // namespace msl
 }  // namespace writer
diff --git a/test/shader_io/invariant.wgsl.expected.msl b/test/shader_io/invariant.wgsl.expected.msl
index 6e1ba46..5543bd0 100644
--- a/test/shader_io/invariant.wgsl.expected.msl
+++ b/test/shader_io/invariant.wgsl.expected.msl
@@ -1,10 +1,12 @@
-SKIP: FAILED
+#include <metal_stdlib>
 
-../../src/writer/msl/generator_impl.cc:1990 internal compiler error: unhandled struct member attribute: invariant
-********************************************************************
-*  The tint shader compiler has encountered an unexpected error.   *
-*                                                                  *
-*  Please help us fix this issue by submitting a bug report at     *
-*  crbug.com/tint with the source program that triggered the bug.  *
-********************************************************************
+using namespace metal;
+struct tint_symbol_1 {
+  float4 value [[position]] [[invariant]];
+};
+
+vertex tint_symbol_1 tint_symbol() {
+  tint_symbol_1 const tint_symbol_2 = {.value=float4()};
+  return tint_symbol_2;
+}
 
diff --git a/test/shader_io/invariant_struct_member.wgsl.expected.msl b/test/shader_io/invariant_struct_member.wgsl.expected.msl
index 6e1ba46..545642e 100644
--- a/test/shader_io/invariant_struct_member.wgsl.expected.msl
+++ b/test/shader_io/invariant_struct_member.wgsl.expected.msl
@@ -1,10 +1,16 @@
-SKIP: FAILED
+#include <metal_stdlib>
 
-../../src/writer/msl/generator_impl.cc:1990 internal compiler error: unhandled struct member attribute: invariant
-********************************************************************
-*  The tint shader compiler has encountered an unexpected error.   *
-*                                                                  *
-*  Please help us fix this issue by submitting a bug report at     *
-*  crbug.com/tint with the source program that triggered the bug.  *
-********************************************************************
+using namespace metal;
+struct Out {
+  float4 pos;
+};
+struct tint_symbol_1 {
+  float4 pos [[position]] [[invariant]];
+};
+
+vertex tint_symbol_1 tint_symbol() {
+  Out const tint_symbol_2 = {};
+  tint_symbol_1 const tint_symbol_3 = {.pos=tint_symbol_2.pos};
+  return tint_symbol_3;
+}