[tint][ir][val] Check position present if invariant is

Fixes: 369788643
Change-Id: I28ce895fceee1bc5a1cbeea78321cb454df4b172
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/209195
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 8c6ad6d..c9ab7c1 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -134,6 +134,12 @@
     return attr.builtin.has_value() && attr.location.has_value();
 }
 
+/// @return true if @param attr does not have invariant decoration or if it also has position
+/// decoration
+bool InvariantOnlyIfAlsoPosition(const tint::core::IOAttributes& attr) {
+    return !attr.invariant || attr.builtin == BuiltinValue::kPosition;
+}
+
 /// @returns true if @p ty meets the basic function parameter rules (i.e. one of constructible,
 ///          pointer, sampler or texture).
 ///
@@ -1191,6 +1197,11 @@
             param->Type(), [&]() -> diag::Diagnostic& { return AddError(param); },
             Capabilities{Capability::kAllowRefTypes});
 
+        if (!InvariantOnlyIfAlsoPosition(param->Attributes())) {
+            AddError(func)
+                << "invariant can only decorate a param iff it is also decorated with position";
+        }
+
         if (!IsValidFunctionParamType(param->Type())) {
             auto struct_ty = param->Type()->As<core::type::Struct>();
             if (!capabilities_.Contains(Capability::kAllowPointersInStructures) || !struct_ty ||
@@ -1210,6 +1221,11 @@
 
         if (auto* s = param->Type()->As<core::type::Struct>()) {
             for (auto* mem : s->Members()) {
+                if (!InvariantOnlyIfAlsoPosition(mem->Attributes())) {
+                    AddError(func) << "invariant can only decorate a param member iff it is also "
+                                      "decorated with position";
+                }
+
                 if (HasLocationAndBuiltin(mem->Attributes())) {
                     AddError(param)
                         << "a builtin and location cannot be both declared for a struct member";
@@ -1264,6 +1280,21 @@
         AddError(func) << "function return type must be constructible";
     }
 
+    const auto* ret_struct = func->ReturnType()->As<core::type::Struct>();
+    if (ret_struct) {
+        for (auto* mem : ret_struct->Members()) {
+            if (!InvariantOnlyIfAlsoPosition(mem->Attributes())) {
+                AddError(func) << "invariant can only decorate a member iff it is also decorated "
+                                  "with position";
+            }
+        }
+    } else {
+        if (!InvariantOnlyIfAlsoPosition(func->ReturnAttributes())) {
+            AddError(func)
+                << "invariant can only decorate a return iff it is also decorated with position";
+        }
+    }
+
     if (func->Stage() != Function::PipelineStage::kFragment) {
         if (DAWN_UNLIKELY(func->ReturnBuiltin().has_value() &&
                           func->ReturnBuiltin().value() == BuiltinValue::kFragDepth)) {
@@ -1273,6 +1304,7 @@
 
     if (func->Stage() == Function::PipelineStage::kVertex) {
         CheckVertexEntryPoint(func);
+    } else {
     }
 
     QueueBlock(func->Block());
@@ -1284,6 +1316,11 @@
     bool contains_position = false;
     if (ret_struct) {
         for (auto* mem : ret_struct->Members()) {
+            if (!InvariantOnlyIfAlsoPosition(mem->Attributes())) {
+                AddError(ep) << "invariant can only decorate output members iff they are also "
+                                "position builtins";
+            }
+
             if (!mem->Attributes().builtin.has_value()) {
                 continue;
             }
@@ -1300,6 +1337,10 @@
             }
         }
     } else {
+        if (!InvariantOnlyIfAlsoPosition(ep->ReturnAttributes())) {
+            AddError(ep)
+                << "invariant can only decorate outputs iff they are also position builtins";
+        }
         if (ep->ReturnBuiltin() && ep->ReturnBuiltin() == BuiltinValue::kPosition) {
             contains_position = true;
             CheckBuiltinPosition(ep, ep->ReturnType());
@@ -1311,6 +1352,11 @@
         const auto* res_struct = res_type->As<core::type::Struct>();
         if (res_struct) {
             for (auto* mem : res_struct->Members()) {
+                if (!InvariantOnlyIfAlsoPosition(mem->Attributes())) {
+                    AddError(ep) << "invariant can only decorate members iff they are also "
+                                    "position builtins";
+                }
+
                 if (!mem->Attributes().builtin.has_value()) {
                     continue;
                 }
@@ -1327,6 +1373,11 @@
                 }
             }
         } else {
+            if (!InvariantOnlyIfAlsoPosition(var->Attributes())) {
+                AddError(ep)
+                    << "invariant can only decorate vars iff they are also position builtins";
+            }
+
             if (!var->Attributes().builtin.has_value()) {
                 continue;
             }
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 33bbf50..80bc71c 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -446,6 +446,104 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Function_Param_InvariantWithPosition) {
+    auto* f = b.Function("my_func", ty.void_());
+    auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
+    IOAttributes attr;
+    attr.builtin = BuiltinValue::kPosition;
+    attr.invariant = true;
+    p->SetAttributes(attr);
+    f->SetParams({p});
+
+    b.Append(f->Block(), [&] { b.Return(f); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, Function_Param_InvariantWithoutPosition) {
+    auto* f = b.Function("my_func", ty.void_());
+    auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
+    IOAttributes attr;
+    attr.invariant = true;
+    p->SetAttributes(attr);
+    f->SetParams({p});
+
+    b.Append(f->Block(), [&] { b.Return(f); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(
+        res.Failure().reason.Str(),
+        R"(:1:1 error: invariant can only decorate a param iff it is also decorated with position
+%my_func = func(%my_param:vec4<f32> [@invariant]):void {
+^^^^^^^^
+
+note: # Disassembly
+%my_func = func(%my_param:vec4<f32> [@invariant]):void {
+  $B1: {
+    ret
+  }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Function_Param_Struct_InvariantWithPosition) {
+    IOAttributes attr;
+    attr.invariant = true;
+    attr.builtin = BuiltinValue::kPosition;
+
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"), {
+                                                   {mod.symbols.New("pos"), ty.vec4<f32>(), attr},
+                                               });
+
+    auto* f = b.Function("my_func", ty.void_());
+    auto* p = b.FunctionParam("my_param", str_ty);
+    f->SetParams({p});
+
+    b.Append(f->Block(), [&] { b.Return(f); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, Function_Param_Struct_InvariantWithoutPosition) {
+    IOAttributes attr;
+    attr.invariant = true;
+
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"), {
+                                                   {mod.symbols.New("pos"), ty.vec4<f32>(), attr},
+                                               });
+
+    auto* f = b.Function("my_func", ty.void_());
+    auto* p = b.FunctionParam("my_param", str_ty);
+    f->SetParams({p});
+
+    b.Append(f->Block(), [&] { b.Return(f); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(
+        res.Failure().reason.Str(),
+        R"(:5:1 error: invariant can only decorate a param member iff it is also decorated with position
+%my_func = func(%my_param:MyStruct):void {
+^^^^^^^^
+
+note: # Disassembly
+MyStruct = struct @align(16) {
+  pos:vec4<f32> @offset(0), @invariant
+}
+
+%my_func = func(%my_param:MyStruct):void {
+  $B1: {
+    ret
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Function_Return_BothLocationAndBuiltin) {
     auto* f = b.Function("my_func", ty.f32());
 
@@ -506,6 +604,98 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Function_Return_InvariantWithPosition) {
+    IOAttributes attr;
+    attr.builtin = BuiltinValue::kPosition;
+    attr.invariant = true;
+
+    auto* f = b.Function("my_func", ty.vec4<f32>());
+    f->SetReturnAttributes(attr);
+
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, Function_Return_InvariantWithoutPosition) {
+    IOAttributes attr;
+    attr.invariant = true;
+
+    auto* f = b.Function("my_func", ty.vec4<f32>());
+    f->SetReturnAttributes(attr);
+
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(
+        res.Failure().reason.Str(),
+        R"(:1:1 error: invariant can only decorate a return iff it is also decorated with position
+%my_func = func():vec4<f32> [@invariant] {
+^^^^^^^^
+
+note: # Disassembly
+%my_func = func():vec4<f32> [@invariant] {
+  $B1: {
+    unreachable
+  }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Function_Return_Struct_InvariantWithPosition) {
+    IOAttributes attr;
+    attr.invariant = true;
+    attr.builtin = BuiltinValue::kPosition;
+
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"), {
+                                                   {mod.symbols.New("pos"), ty.vec4<f32>(), attr},
+                                               });
+
+    auto* f = b.Function("my_func", str_ty);
+
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_EQ(res, Success);
+}
+
+TEST_F(IR_ValidatorTest, Function_Return_Struct_InvariantWithoutPosition) {
+    IOAttributes attr;
+    attr.invariant = true;
+
+    auto* str_ty =
+        ty.Struct(mod.symbols.New("MyStruct"), {
+                                                   {mod.symbols.New("pos"), ty.vec4<f32>(), attr},
+                                               });
+
+    auto* f = b.Function("my_func", str_ty);
+
+    b.Append(f->Block(), [&] { b.Unreachable(); });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(
+        res.Failure().reason.Str(),
+        R"(:5:1 error: invariant can only decorate a member iff it is also decorated with position
+%my_func = func():MyStruct {
+^^^^^^^^
+
+note: # Disassembly
+MyStruct = struct @align(16) {
+  pos:vec4<f32> @offset(0), @invariant
+}
+
+%my_func = func():MyStruct {
+  $B1: {
+    unreachable
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Function_MissingWorkgroupSize) {
     auto* f = b.Function("f", ty.void_(), Function::PipelineStage::kCompute);
     b.Append(f->Block(), [&] { b.Return(f); });