validation: atomics access mode and storage class
Bug: tint:901 tint:909
Change-Id: Ibbcdd80ddbe2aa9940bbd73bb404349afc633836
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/60080
Auto-Submit: Sarah Mashayekhi <sarahmashay@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Sarah Mashayekhi <sarahmashay@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/resolver/atomics_validation_test.cc b/src/resolver/atomics_validation_test.cc
index 73181dc..15aa2d6 100644
--- a/src/resolver/atomics_validation_test.cc
+++ b/src/resolver/atomics_validation_test.cc
@@ -26,7 +26,23 @@
struct ResolverAtomicValidationTest : public resolver::TestHelper,
public testing::Test {};
-TEST_F(ResolverAtomicValidationTest, GlobalOfInvalidType) {
+TEST_F(ResolverAtomicValidationTest, StorageClass_WorkGroup) {
+ Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
+ ast::StorageClass::kWorkgroup);
+
+ EXPECT_TRUE(r()->Resolve());
+}
+
+TEST_F(ResolverAtomicValidationTest, StorageClass_Storage) {
+ auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
+ {StructBlock()});
+ Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
+ GroupAndBinding(0, 0));
+
+ EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidType) {
Global("a", ty.atomic(ty.f32(Source{{12, 34}})),
ast::StorageClass::kWorkgroup);
@@ -34,28 +50,274 @@
EXPECT_EQ(r()->error(), "12:34 error: atomic only supports i32 or u32 types");
}
-// TODO(crbug.com/tint/909): add validation and enable this test
-TEST_F(ResolverAtomicValidationTest, DISABLED_GlobalOfInvalidStorageClass) {
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Simple) {
Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
- EXPECT_EQ(r()->error(), "12:34 error: atomic var requires workgroup storage");
+ EXPECT_EQ(r()->error(),
+ "12:34 error: atomic variables must have <storage> or <workgroup> "
+ "storage class");
}
-TEST_F(ResolverAtomicValidationTest, GlobalPrivateStruct) {
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Array) {
+ Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
+ ast::StorageClass::kPrivate);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "12:34 error: atomic variables must have <storage> or <workgroup> "
+ "storage class");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Struct) {
auto* s =
Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
Global("g", ty.Of(s), ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
- "12:34 error: atomic types can only be used in storage classes "
- "workgroup or storage, but was used by storage class private");
+ "error: atomic variables must have <storage> or <workgroup> "
+ "storage class\n"
+ "note: atomic sub-type of 's' is declared here");
}
-// TODO(crbug.com/tint/901): Validate that storage buffer access mode is
-// read_write.
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_StructOfStruct) {
+ // struct Inner { m : atomic<i32>; };
+ // struct Outer { m : array<Inner, 4>; };
+ // var<private> g : Outer;
+
+ auto* Inner =
+ Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
+ auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+ Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables must have <storage> or <workgroup> "
+ "storage class\n"
+ "note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest,
+ InvalidStorageClass_StructOfStructOfArray) {
+ // struct Inner { m : array<atomic<i32>, 4>; };
+ // struct Outer { m : array<Inner, 4>; };
+ // var<private> g : Outer;
+
+ auto* Inner =
+ Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
+ auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
+ Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables must have <storage> or <workgroup> "
+ "storage class\n"
+ "12:34 note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfArray) {
+ // type AtomicArray = array<atomic<i32>, 5>;
+ // var<private> v: array<s, 5>;
+
+ auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+ ty.atomic(Source{{12, 34}}, ty.i32()));
+ Global(Source{{56, 78}}, "v", ty.Of(atomic_array),
+ ast::StorageClass::kPrivate);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables must have <storage> or <workgroup> "
+ "storage class");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStruct) {
+ // struct S{
+ // m: atomic<u32>;
+ // };
+ // var<private> v: array<S, 5>;
+
+ auto* s = Structure("S", {Member("m", ty.atomic<u32>())});
+ Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
+ ast::StorageClass::kPrivate);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables must have <storage> or <workgroup> "
+ "storage class\n"
+ "note: atomic sub-type of 'array<S, 5>' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStructOfArray) {
+ // type AtomicArray = array<atomic<i32>, 5>;
+ // struct S{
+ // m: AtomicArray;
+ // };
+ // var<private> v: array<S, 5>;
+
+ auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+ ty.atomic(Source{{12, 34}}, ty.i32()));
+ auto* s = Structure("S", {Member("m", ty.Of(atomic_array))});
+ Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
+ ast::StorageClass::kPrivate);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables must have <storage> or <workgroup> "
+ "storage class\n"
+ "note: atomic sub-type of 'array<S, 5>' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Complex) {
+ // type AtomicArray = array<atomic<i32>, 5>;
+ // struct S6 { x: array<i32, 4>; };
+ // struct S5 { x: S6;
+ // y: AtomicArray;
+ // z: array<atomic<u32>, 8>; };
+ // struct S4 { x: S6;
+ // y: S5;
+ // z: array<atomic<i32>, 4>; };
+ // struct S3 { x: S4; };
+ // struct S2 { x: S3; };
+ // struct S1 { x: S2; };
+ // struct S0 { x: S1; };
+ // var<private> g : S0;
+
+ auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+ ty.atomic(Source{{12, 34}}, ty.i32()));
+ auto* array_i32_4 = ty.array(ty.i32(), 4);
+ auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
+ auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
+
+ auto* s6 = Structure("S6", {Member("x", array_i32_4)});
+ auto* s5 = Structure("S5", {Member("x", ty.Of(s6)), //
+ Member("y", ty.Of(atomic_array)), //
+ Member("z", array_atomic_u32_8)}); //
+ auto* s4 = Structure("S4", {Member("x", ty.Of(s6)), //
+ Member("y", ty.Of(s5)), //
+ Member("z", array_atomic_i32_4)}); //
+ auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
+ auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
+ auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
+ auto* s0 = Structure("S0", {Member("x", ty.Of(s1))});
+ Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kPrivate);
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables must have <storage> or <workgroup> "
+ "storage class\n"
+ "note: atomic sub-type of 'S0' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, Struct_AccessMode_Read) {
+ auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
+ {StructBlock()});
+ Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+ ast::Access::kRead, GroupAndBinding(0, 0));
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ "error: atomic variables in <storage> storage class must have read_write "
+ "access mode\n"
+ "note: atomic sub-type of 's' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Struct) {
+ auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
+ {StructBlock()});
+ Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
+ ast::Access::kRead, GroupAndBinding(0, 0));
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ "error: atomic variables in <storage> storage class must have read_write "
+ "access mode\n"
+ "note: atomic sub-type of 's' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStruct) {
+ // struct Inner { m : atomic<i32>; };
+ // struct Outer { m : array<Inner, 4>; };
+ // var<storage, read> g : Outer;
+
+ auto* Inner =
+ Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
+ auto* Outer =
+ Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
+ Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
+ ast::Access::kRead, GroupAndBinding(0, 0));
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(
+ r()->error(),
+ "error: atomic variables in <storage> storage class must have read_write "
+ "access mode\n"
+ "note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStructOfArray) {
+ // struct Inner { m : array<atomic<i32>, 4>; };
+ // struct Outer { m : array<Inner, 4>; };
+ // var<storage, read> g : Outer;
+
+ auto* Inner =
+ Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
+ auto* Outer =
+ Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
+ Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
+ ast::Access::kRead, GroupAndBinding(0, 0));
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables in <storage> storage class must have "
+ "read_write access mode\n"
+ "12:34 note: atomic sub-type of 'Outer' is declared here");
+}
+
+TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Complex) {
+ // type AtomicArray = array<atomic<i32>, 5>;
+ // struct S6 { x: array<i32, 4>; };
+ // struct S5 { x: S6;
+ // y: AtomicArray;
+ // z: array<atomic<u32>, 8>; };
+ // struct S4 { x: S6;
+ // y: S5;
+ // z: array<atomic<i32>, 4>; };
+ // struct S3 { x: S4; };
+ // struct S2 { x: S3; };
+ // struct S1 { x: S2; };
+ // struct S0 { x: S1; };
+ // var<storage, read> g : S0;
+
+ auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
+ ty.atomic(Source{{12, 34}}, ty.i32()));
+ auto* array_i32_4 = ty.array(ty.i32(), 4);
+ auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
+ auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
+
+ auto* s6 = Structure("S6", {Member("x", array_i32_4)});
+ auto* s5 = Structure("S5", {Member("x", ty.Of(s6)), //
+ Member("y", ty.Of(atomic_array)), //
+ Member("z", array_atomic_u32_8)}); //
+ auto* s4 = Structure("S4", {Member("x", ty.Of(s6)), //
+ Member("y", ty.Of(s5)), //
+ Member("z", array_atomic_i32_4)}); //
+ auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
+ auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
+ auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
+ auto* s0 = Structure("S0", {Member("x", ty.Of(s1))}, {StructBlock()});
+ Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kStorage,
+ ast::Access::kRead, GroupAndBinding(0, 0));
+
+ EXPECT_FALSE(r()->Resolve());
+ EXPECT_EQ(r()->error(),
+ "error: atomic variables in <storage> storage class must have "
+ "read_write access mode\n"
+ "note: atomic sub-type of 'S0' is declared here");
+}
TEST_F(ResolverAtomicValidationTest, Local) {
WrapInFunction(Var("a", ty.atomic(Source{{12, 34}}, ty.i32())));
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index a1ad4ae..e746bc1 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -265,12 +265,8 @@
if (!ValidatePipelineStages()) {
return false;
}
- if (!ValidateAtomicUses()) {
- return false;
- }
bool result = true;
-
for (auto* node : builder_->ASTNodes().Objects()) {
if (marked_.count(node) == 0) {
TINT_ICE(Resolver, diagnostics_)
@@ -421,34 +417,6 @@
return true;
}
-bool Resolver::ValidateAtomicUses() {
- // https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
- // Atomic types may only be instantiated by variables in the workgroup storage
- // class or by storage buffer variables with a read_write access mode.
- for (auto sm : atomic_members_) {
- auto* structure = sm.structure;
- for (auto usage : structure->StorageClassUsage()) {
- if (usage == ast::StorageClass::kWorkgroup) {
- continue;
- }
- if (usage != ast::StorageClass::kStorage) {
- // TODO(crbug.com/tint/901): Validate that the access mode is
- // read_write.
- auto* member = structure->Members()[sm.index];
- AddError(
- "atomic types can only be used in storage classes workgroup or "
- "storage, but was used by storage class " +
- std::string(ast::str(usage)),
- member->Declaration()->type()->source());
- // TODO(crbug.com/tint/901): Add note pointing at where the usage came
- // from.
- return false;
- }
- }
- }
- return true;
-}
-
bool Resolver::ValidateStorageTexture(const ast::StorageTexture* t) {
switch (t->access()) {
case ast::Access::kUndefined:
@@ -979,8 +947,7 @@
if (info->storage_class != ast::StorageClass::kStorage &&
info->declaration->declared_access() != ast::Access::kUndefined) {
AddError(
- "variables not in <storage> storage class must not declare an access "
- "mode",
+ "only variables in <storage> storage class may declare an access mode",
info->declaration->source());
return false;
}
@@ -1060,9 +1027,62 @@
break;
}
+ if (!info->declaration->is_const()) {
+ if (!ValidateAtomicVariable(info)) {
+ return false;
+ }
+ }
+
return ValidateVariable(info);
}
+// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
+// Atomic types may only be instantiated by variables in the workgroup storage
+// class or by storage buffer variables with a read_write access mode.
+bool Resolver::ValidateAtomicVariable(const VariableInfo* info) {
+ auto sc = info->storage_class;
+ auto access = info->access;
+ auto* type = info->type->UnwrapRef();
+ auto source = info->declaration->type()->source();
+
+ if (type->Is<sem::Atomic>()) {
+ if (sc != ast::StorageClass::kWorkgroup) {
+ AddError(
+ "atomic variables must have <storage> or <workgroup> storage class",
+ info->declaration->type()->source());
+ return false;
+ }
+ } else if (type->IsAnyOf<sem::Struct, sem::Array>()) {
+ auto found = atomic_composite_info_.find(type);
+ if (found != atomic_composite_info_.end()) {
+ if (sc != ast::StorageClass::kStorage &&
+ sc != ast::StorageClass::kWorkgroup) {
+ AddError(
+ "atomic variables must have <storage> or <workgroup> storage class",
+ source);
+ AddNote("atomic sub-type of '" +
+ type->FriendlyName(builder_->Symbols()) +
+ "' is declared here",
+ found->second);
+ return false;
+ } else if (sc == ast::StorageClass::kStorage &&
+ access != ast::Access::kReadWrite) {
+ AddError(
+ "atomic variables in <storage> storage class must have read_write "
+ "access mode",
+ source);
+ AddNote("atomic sub-type of '" +
+ type->FriendlyName(builder_->Symbols()) +
+ "' is declared here",
+ found->second);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
bool Resolver::ValidateVariable(const VariableInfo* info) {
auto* var = info->declaration;
auto* storage_type = info->type->UnwrapRef();
@@ -3738,20 +3758,20 @@
sem::Array* Resolver::Array(const ast::Array* arr) {
auto source = arr->source();
- auto* el_ty = Type(arr->type());
- if (!el_ty) {
+ auto* elem_type = Type(arr->type());
+ if (!elem_type) {
return nullptr;
}
- if (!IsPlain(el_ty)) { // Check must come before GetDefaultAlignAndSize()
- AddError(el_ty->FriendlyName(builder_->Symbols()) +
+ if (!IsPlain(elem_type)) { // Check must come before GetDefaultAlignAndSize()
+ AddError(elem_type->FriendlyName(builder_->Symbols()) +
" cannot be used as an element type of an array",
source);
return nullptr;
}
- uint32_t el_align = el_ty->Align();
- uint32_t el_size = el_ty->Size();
+ uint32_t el_align = elem_type->Align();
+ uint32_t el_size = elem_type->Size();
if (!ValidateNoDuplicateDecorations(arr->decorations())) {
return nullptr;
@@ -3781,14 +3801,23 @@
// WebGPU requires runtime arrays have at least one element, but the AST
// records an element count of 0 for it.
auto size = std::max<uint32_t>(arr->size(), 1) * stride;
- auto* sem = builder_->create<sem::Array>(el_ty, arr->size(), el_align, size,
- stride, implicit_stride);
+ auto* out = builder_->create<sem::Array>(elem_type, arr->size(), el_align,
+ size, stride, implicit_stride);
- if (!ValidateArray(sem, source)) {
+ if (!ValidateArray(out, source)) {
return nullptr;
}
- return sem;
+ if (elem_type->Is<sem::Atomic>()) {
+ atomic_composite_info_.emplace(out, arr->type()->source());
+ } else {
+ auto found = atomic_composite_info_.find(elem_type);
+ if (found != atomic_composite_info_.end()) {
+ atomic_composite_info_.emplace(out, found->second);
+ }
+ }
+
+ return out;
}
bool Resolver::ValidateArray(const sem::Array* arr, const Source& source) {
@@ -4090,11 +4119,18 @@
builder_->create<sem::Struct>(str, str->name(), sem_members, struct_align,
struct_size, size_no_padding);
- // Keep track of atomic members for validation after all usages have been
- // determined.
for (size_t i = 0; i < sem_members.size(); i++) {
- if (sem_members[i]->Type()->Is<sem::Atomic>()) {
- atomic_members_.emplace_back(StructMember{out, i});
+ auto* mem_type = sem_members[i]->Type();
+ if (mem_type->Is<sem::Atomic>()) {
+ atomic_composite_info_.emplace(out,
+ sem_members[i]->Declaration()->source());
+ break;
+ } else {
+ auto found = atomic_composite_info_.find(mem_type);
+ if (found != atomic_composite_info_.end()) {
+ atomic_composite_info_.emplace(out, found->second);
+ break;
+ }
}
}
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 86074f2..37de090 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -207,13 +207,6 @@
sem::Type* const sem;
};
- // Structure holding a pointer to the sem::Struct and an index to a member of
- // that structure.
- struct StructMember {
- sem::Struct* structure;
- size_t index;
- };
-
/// Resolves the program, without creating final the semantic nodes.
/// @returns true on success, false on error
bool ResolveInternal();
@@ -277,7 +270,7 @@
uint32_t el_align,
const Source& source);
bool ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s);
- bool ValidateAtomicUses();
+ bool ValidateAtomicVariable(const VariableInfo* info);
bool ValidateAssignment(const ast::AssignmentStatement* a);
bool ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
const sem::Type* storage_type,
@@ -470,7 +463,7 @@
ScopeStack<VariableInfo*> variable_stack_;
std::unordered_map<Symbol, FunctionInfo*> symbol_to_function_;
std::vector<FunctionInfo*> entry_points_;
- std::vector<StructMember> atomic_members_;
+ std::unordered_map<const sem::Type*, const Source&> atomic_composite_info_;
std::unordered_map<const ast::Function*, FunctionInfo*> function_to_info_;
std::unordered_map<const ast::Variable*, VariableInfo*> variable_to_info_;
std::unordered_map<const ast::CallExpression*, FunctionCallInfo>
diff --git a/src/resolver/storage_class_validation_test.cc b/src/resolver/storage_class_validation_test.cc
index 89fa428..9a60ac4 100644
--- a/src/resolver/storage_class_validation_test.cc
+++ b/src/resolver/storage_class_validation_test.cc
@@ -108,7 +108,7 @@
EXPECT_EQ(
r()->error(),
- R"(56:78 error: variables not in <storage> storage class must not declare an access mode)");
+ R"(56:78 error: only variables in <storage> storage class may declare an access mode)");
}
TEST_F(ResolverStorageClassValidationTest, StorageBufferNoBlockDecoration) {