resolver: Resolve atomic types

Bug: tint:892
Change-Id: Ib595378b3b126a8f6bc4712ece943ba70816cb46
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/54647
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2021-06-17 19:56:14 +00:00 committed by Tint LUCI CQ
parent cf1613ee35
commit 313e6184e6
9 changed files with 330 additions and 41 deletions

View File

@ -591,6 +591,8 @@ if(${TINT_BUILD_TESTS})
intrinsic_table_test.cc
program_test.cc
resolver/assignment_validation_test.cc
resolver/atomics_test.cc
resolver/atomics_validation_test.cc
resolver/block_test.cc
resolver/builtins_validation_test.cc
resolver/call_test.cc

View File

@ -745,6 +745,12 @@ class ProgramBuilder {
return builder->create<ast::Atomic>(source, type);
}
/// @param type the type of the atomic
/// @return the atomic to `type`
ast::Atomic* atomic(ast::Type* type) const {
return builder->create<ast::Atomic>(type);
}
/// @return the atomic to type `T`
template <typename T>
ast::Atomic* atomic() const {
@ -1079,6 +1085,19 @@ class ProgramBuilder {
type, ExprList(std::forward<ARGS>(args)...));
}
/// @param source the source information
/// @param type the type to construct
/// @param args the arguments for the constructor
/// @return an `ast::TypeConstructorExpression` of `type` constructed with the
/// values `args`.
template <typename... ARGS>
ast::TypeConstructorExpression* Construct(const Source& source,
ast::Type* type,
ARGS&&... args) {
return create<ast::TypeConstructorExpression>(
source, type, ExprList(std::forward<ARGS>(args)...));
}
/// @param args the arguments for the vector constructor
/// @return an `ast::TypeConstructorExpression` of a 2-element vector of type
/// `T`, constructed with the values `args`.

View File

@ -0,0 +1,74 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/ast/struct_block_decoration.h"
#include "src/resolver/resolver.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/sem/atomic_type.h"
#include "src/sem/reference_type.h"
#include "gmock/gmock.h"
namespace tint {
namespace resolver {
namespace {
struct ResolverAtomicTest : public resolver::TestHelper,
public testing::Test {};
TEST_F(ResolverAtomicTest, GlobalWorkgroupI32) {
auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
ast::StorageClass::kWorkgroup);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
ASSERT_NE(atomic, nullptr);
EXPECT_TRUE(atomic->Type()->Is<sem::I32>());
}
TEST_F(ResolverAtomicTest, GlobalWorkgroupU32) {
auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.u32()),
ast::StorageClass::kWorkgroup);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
ASSERT_NE(atomic, nullptr);
EXPECT_TRUE(atomic->Type()->Is<sem::U32>());
}
TEST_F(ResolverAtomicTest, GlobalStorageStruct) {
auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
{create<ast::StructBlockDecoration>()});
auto* g = Global("g", ty.Of(s), ast::StorageClass::kStorage,
ast::Access::kReadWrite,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
auto* str = TypeOf(g)->UnwrapRef()->As<sem::Struct>();
ASSERT_NE(str, nullptr);
ASSERT_EQ(str->Members().size(), 1u);
auto* atomic = str->Members()[0]->Type()->As<sem::Atomic>();
ASSERT_NE(atomic, nullptr);
ASSERT_TRUE(atomic->Type()->Is<sem::I32>());
}
} // namespace
} // namespace resolver
} // namespace tint

View File

@ -0,0 +1,77 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/resolver/resolver.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/sem/atomic_type.h"
#include "src/sem/reference_type.h"
#include "gmock/gmock.h"
namespace tint {
namespace resolver {
namespace {
struct ResolverAtomicValidationTest : public resolver::TestHelper,
public testing::Test {};
TEST_F(ResolverAtomicValidationTest, GlobalOfInvalidType) {
Global("a", ty.atomic(ty.f32(Source{{12, 34}})),
ast::StorageClass::kWorkgroup);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: atomic only supports i32 or u32 types");
}
TEST_F(ResolverAtomicValidationTest, GlobalOfInvalidStorageClass) {
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");
}
TEST_F(ResolverAtomicValidationTest, GlobalPrivateStruct) {
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");
}
// TODO(crbug.com/tint/901): Validate that storage buffer access mode is
// read_write.
TEST_F(ResolverAtomicValidationTest, Local) {
WrapInFunction(Var("a", ty.atomic(Source{{12, 34}}, ty.i32())));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: cannot declare an atomic var in a function scope");
}
TEST_F(ResolverAtomicValidationTest, NoAtomicExpr) {
WrapInFunction(Construct(Source{{12, 34}}, ty.atomic<u32>()));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: an expression must not evaluate to an atomic type");
}
} // namespace
} // namespace resolver
} // namespace tint

View File

@ -16,6 +16,7 @@
#include "gmock/gmock.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/sem/atomic_type.h"
namespace tint {
namespace resolver {
@ -92,6 +93,11 @@ TEST_F(ResolverIsHostShareable, Pointer) {
EXPECT_FALSE(r()->IsHostShareable(ptr));
}
TEST_F(ResolverIsHostShareable, Atomic) {
EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::I32>())));
EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::U32>())));
}
TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, true);
EXPECT_TRUE(r()->IsHostShareable(arr));

View File

@ -16,6 +16,7 @@
#include "gmock/gmock.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/sem/atomic_type.h"
namespace tint {
namespace resolver {
@ -67,6 +68,11 @@ TEST_F(ResolverIsStorableTest, Pointer) {
EXPECT_FALSE(r()->IsStorable(ptr));
}
TEST_F(ResolverIsStorableTest, Atomic) {
EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::I32>())));
EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::U32>())));
}
TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, true);
EXPECT_TRUE(r()->IsStorable(arr));

View File

@ -46,6 +46,7 @@
#include "src/ast/vector.h"
#include "src/ast/workgroup_decoration.h"
#include "src/sem/array.h"
#include "src/sem/atomic_type.h"
#include "src/sem/call.h"
#include "src/sem/depth_texture_type.h"
#include "src/sem/function.h"
@ -166,19 +167,14 @@ bool Resolver::Resolve() {
// https://gpuweb.github.io/gpuweb/wgsl/#plain-types-section
bool Resolver::IsPlain(const sem::Type* type) {
if (type->is_scalar() || type->Is<sem::Vector>() || type->Is<sem::Matrix>() ||
type->Is<sem::Array>() || type->Is<sem::Struct>()) {
return true;
}
return false;
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.html#storable-types
bool Resolver::IsStorable(const sem::Type* type) {
if (IsPlain(type) || type->Is<sem::Texture>() || type->Is<sem::Sampler>()) {
return true;
}
return false;
return IsPlain(type) || type->Is<sem::Texture>() || type->Is<sem::Sampler>();
}
// https://gpuweb.github.io/gpuweb/wgsl.html#host-shareable-types
@ -203,6 +199,9 @@ bool Resolver::IsHostShareable(const sem::Type* type) {
}
return true;
}
if (auto* atomic = type->As<sem::Atomic>()) {
return IsHostShareable(atomic->Type());
}
return false;
}
@ -237,6 +236,9 @@ bool Resolver::ResolveInternal() {
if (!ValidatePipelineStages()) {
return false;
}
if (!ValidateAtomicUses()) {
return false;
}
bool result = true;
@ -290,6 +292,16 @@ sem::Type* Resolver::Type(const ast::Type* ty) {
if (auto* t = ty->As<ast::Array>()) {
return Array(t);
}
if (auto* t = ty->As<ast::Atomic>()) {
if (auto* el = Type(t->type())) {
auto* a = builder_->create<sem::Atomic>(const_cast<sem::Type*>(el));
if (!ValidateAtomic(t, a)) {
return nullptr;
}
return a;
}
return nullptr;
}
if (auto* t = ty->As<ast::Pointer>()) {
if (auto* el = Type(t->type())) {
auto access = t->access();
@ -356,6 +368,45 @@ sem::Type* Resolver::Type(const ast::Type* ty) {
return s;
}
bool Resolver::ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s) {
// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
// T must be either u32 or i32.
if (!s->Type()->IsAnyOf<sem::U32, sem::I32>()) {
diagnostics_.add_error("atomic only supports i32 or u32 types",
a->type()->source());
return false;
}
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];
diagnostics_.add_error(
"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:
@ -816,6 +867,26 @@ bool Resolver::ValidateVariable(const VariableInfo* info) {
return false;
}
// 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.
if (info->type->UnwrapRef()->Is<sem::Atomic>()) {
if (info->kind != VariableKind::kGlobal) {
// Neither storage nor workgroup storage classes can be used in function
// scopes.
diagnostics_.add_error("cannot declare an atomic var in a function scope",
info->declaration->type()->source());
return false;
}
if (info->storage_class != ast::StorageClass::kWorkgroup) {
// Storage buffers require a structure, so just check for workgroup
// storage here.
diagnostics_.add_error("atomic var requires workgroup storage",
info->declaration->type()->source());
return false;
}
}
return true;
}
@ -1620,34 +1691,40 @@ bool Resolver::Expression(ast::Expression* expr) {
return true; // Already resolved
}
if (auto* a = expr->As<ast::ArrayAccessorExpression>()) {
return ArrayAccessor(a);
}
if (auto* b = expr->As<ast::BinaryExpression>()) {
return Binary(b);
}
if (auto* b = expr->As<ast::BitcastExpression>()) {
return Bitcast(b);
}
if (auto* c = expr->As<ast::CallExpression>()) {
return Call(c);
}
if (auto* c = expr->As<ast::ConstructorExpression>()) {
return Constructor(c);
}
if (auto* i = expr->As<ast::IdentifierExpression>()) {
return Identifier(i);
}
if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
return MemberAccessor(m);
}
if (auto* u = expr->As<ast::UnaryOpExpression>()) {
return UnaryOp(u);
bool ok = false;
if (auto* array = expr->As<ast::ArrayAccessorExpression>()) {
ok = ArrayAccessor(array);
} else if (auto* bin_op = expr->As<ast::BinaryExpression>()) {
ok = Binary(bin_op);
} else if (auto* bitcast = expr->As<ast::BitcastExpression>()) {
ok = Bitcast(bitcast);
} else if (auto* call = expr->As<ast::CallExpression>()) {
ok = Call(call);
} else if (auto* ctor = expr->As<ast::ConstructorExpression>()) {
ok = Constructor(ctor);
} else if (auto* ident = expr->As<ast::IdentifierExpression>()) {
ok = Identifier(ident);
} else if (auto* member = expr->As<ast::MemberAccessorExpression>()) {
ok = MemberAccessor(member);
} else if (auto* unary = expr->As<ast::UnaryOpExpression>()) {
ok = UnaryOp(unary);
} else {
diagnostics_.add_error("unknown expression for type determination",
expr->source());
}
diagnostics_.add_error("unknown expression for type determination",
expr->source());
return false;
if (!ok) {
return false;
}
auto* ty = TypeOf(expr);
if (ty->Is<sem::Atomic>()) {
diagnostics_.add_error("an expression must not evaluate to an atomic type",
expr->source());
return false;
}
return true;
}
bool Resolver::ArrayAccessor(ast::ArrayAccessorExpression* expr) {
@ -2883,7 +2960,8 @@ bool Resolver::DefaultAlignAndSize(const sem::Type* ty,
align = 4;
size = 4;
return true;
} else if (auto* vec = ty->As<sem::Vector>()) {
}
if (auto* vec = ty->As<sem::Vector>()) {
if (vec->size() < 2 || vec->size() > 4) {
TINT_UNREACHABLE(diagnostics_)
<< "Invalid vector size: vec" << vec->size();
@ -2892,7 +2970,8 @@ bool Resolver::DefaultAlignAndSize(const sem::Type* ty,
align = vector_align[vec->size()];
size = vector_size[vec->size()];
return true;
} else if (auto* mat = ty->As<sem::Matrix>()) {
}
if (auto* mat = ty->As<sem::Matrix>()) {
if (mat->columns() < 2 || mat->columns() > 4 || mat->rows() < 2 ||
mat->rows() > 4) {
TINT_UNREACHABLE(diagnostics_)
@ -2902,15 +2981,20 @@ bool Resolver::DefaultAlignAndSize(const sem::Type* ty,
align = vector_align[mat->rows()];
size = vector_align[mat->rows()] * mat->columns();
return true;
} else if (auto* s = ty->As<sem::Struct>()) {
}
if (auto* s = ty->As<sem::Struct>()) {
align = s->Align();
size = s->Size();
return true;
} else if (auto* a = ty->As<sem::Array>()) {
}
if (auto* a = ty->As<sem::Array>()) {
align = a->Align();
size = a->SizeInBytes();
return true;
}
if (auto* a = ty->As<sem::Atomic>()) {
return DefaultAlignAndSize(a->Type(), align, size);
}
TINT_UNREACHABLE(diagnostics_) << "invalid type " << ty->TypeInfo().name;
return false;
}
@ -3187,8 +3271,16 @@ sem::Struct* Resolver::Structure(const ast::Struct* str) {
auto size_no_padding = struct_size;
struct_size = utils::RoundUp(struct_align, struct_size);
auto* out = builder_->create<sem::Struct>(
str, std::move(sem_members), struct_align, struct_size, size_no_padding);
auto* out = builder_->create<sem::Struct>(str, 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});
}
}
if (!ValidateStructure(out)) {
return nullptr;

View File

@ -52,6 +52,7 @@ class Variable;
} // namespace ast
namespace sem {
class Array;
class Atomic;
class Intrinsic;
class Statement;
} // namespace sem
@ -196,6 +197,13 @@ class Resolver {
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();
@ -255,6 +263,8 @@ class Resolver {
uint32_t el_size,
uint32_t el_align,
const Source& source);
bool ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s);
bool ValidateAtomicUses();
bool ValidateAssignment(const ast::AssignmentStatement* a);
bool ValidateCallStatement(ast::CallStatement* stmt);
bool ValidateEntryPoint(const ast::Function* func, const FunctionInfo* info);
@ -401,6 +411,7 @@ class Resolver {
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 ast::Function*, FunctionInfo*> function_to_info_;
std::unordered_map<const ast::Variable*, VariableInfo*> variable_to_info_;
std::unordered_map<const ast::CallExpression*, FunctionCallInfo>

View File

@ -222,6 +222,8 @@ tint_unittests_source_set("tint_unittests_core_src") {
"../src/program_builder_test.cc",
"../src/program_test.cc",
"../src/resolver/assignment_validation_test.cc",
"../src/resolver/atomics_test.cc",
"../src/resolver/atomics_validation_test.cc",
"../src/resolver/block_test.cc",
"../src/resolver/builtins_validation_test.cc",
"../src/resolver/call_test.cc",