Validate that functions return atomic-free plain types

Bug: tint:876
Change-Id: If00aa2c64fc3039d6271d3c5e35c05635a6bf3d1
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/55480
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
index 93852c8..0771eeb 100644
--- a/src/resolver/function_validation_test.cc
+++ b/src/resolver/function_validation_test.cc
@@ -479,5 +479,47 @@
             "i32 module-scope constant");
 }
 
+TEST_F(ResolverFunctionValidationTest, ReturnIsAtomicFreePlain_NonPlain) {
+  auto* ret_type =
+      ty.pointer(Source{{12, 34}}, ty.i32(), ast::StorageClass::kFunction);
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: function return type must be an atomic-free plain type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsAtomicFreePlain_AtomicInt) {
+  auto* ret_type = ty.atomic(Source{{12, 34}}, ty.i32());
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: function return type must be an atomic-free plain type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsAtomicFreePlain_ArrayOfAtomic) {
+  auto* ret_type = ty.array(Source{{12, 34}}, ty.atomic(ty.i32()));
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: function return type must be an atomic-free plain type");
+}
+
+TEST_F(ResolverFunctionValidationTest, ReturnIsAtomicFreePlain_StructOfAtomic) {
+  Structure("S", {Member("m", ty.atomic(ty.i32()))});
+  auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
+  Func("f", {}, ret_type, {});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error: function return type must be an atomic-free plain type");
+}
+
 }  // namespace
 }  // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 25b1dbf..5d01d64 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -176,19 +176,45 @@
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl/#plain-types-section
-bool Resolver::IsPlain(const sem::Type* type) {
+bool Resolver::IsPlain(const sem::Type* type) const {
   return type->is_scalar() || type->Is<sem::Atomic>() ||
          type->Is<sem::Vector>() || type->Is<sem::Matrix>() ||
          type->Is<sem::Array>() || type->Is<sem::Struct>();
 }
 
+// https://gpuweb.github.io/gpuweb/wgsl/#atomic-free
+bool Resolver::IsAtomicFreePlain(const sem::Type* type) const {
+  if (type->Is<sem::Atomic>()) {
+    return false;
+  }
+
+  if (type->is_scalar() || type->Is<sem::Vector>() || type->Is<sem::Matrix>()) {
+    return true;
+  }
+
+  if (auto* arr = type->As<sem::Array>()) {
+    return IsAtomicFreePlain(arr->ElemType());
+  }
+
+  if (auto* str = type->As<sem::Struct>()) {
+    for (auto* m : str->Members()) {
+      if (!IsAtomicFreePlain(m->Type())) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  return false;
+}
+
 // https://gpuweb.github.io/gpuweb/wgsl.html#storable-types
-bool Resolver::IsStorable(const sem::Type* type) {
+bool Resolver::IsStorable(const sem::Type* type) const {
   return IsPlain(type) || type->Is<sem::Texture>() || type->Is<sem::Sampler>();
 }
 
 // https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable-types
-bool Resolver::IsHostShareable(const sem::Type* type) {
+bool Resolver::IsHostShareable(const sem::Type* type) const {
   if (type->IsAnyOf<sem::I32, sem::U32, sem::F32>()) {
     return true;
   }
@@ -1013,6 +1039,13 @@
   }
 
   if (!info->return_type->Is<sem::Void>()) {
+    if (!IsAtomicFreePlain(info->return_type)) {
+      diagnostics_.add_error(
+          "function return type must be an atomic-free plain type",
+          func->return_type()->source());
+      return false;
+    }
+
     if (func->body()) {
       if (!func->get_last_statement() ||
           !func->get_last_statement()->Is<ast::ReturnStatement>()) {
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index a8930ae..cf303c0 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -77,15 +77,19 @@
 
   /// @param type the given type
   /// @returns true if the given type is a plain type
-  bool IsPlain(const sem::Type* type);
+  bool IsPlain(const sem::Type* type) const;
+
+  /// @param type the given type
+  /// @returns true if the given type is a atomic-free plain type
+  bool IsAtomicFreePlain(const sem::Type* type) const;
 
   /// @param type the given type
   /// @returns true if the given type is storable
-  bool IsStorable(const sem::Type* type);
+  bool IsStorable(const sem::Type* type) const;
 
   /// @param type the given type
   /// @returns true if the given type is host-shareable
-  bool IsHostShareable(const sem::Type* type);
+  bool IsHostShareable(const sem::Type* type) const;
 
  private:
   /// Describes the context in which a variable is declared