Move struct validation from Validator to Resolver

* Moved Validator::ValidateConstructedType, which only validated
structs, to Resolver as ValidateStructure.
* Moved relevant tests to new files, and also updated all failing tests
to validate Source location.
* Fixed other tests that broke now that we're validating structs.

Bug: tint:642
Change-Id: Iefc08ef548f52d8c3798d814d2183c56d1236c2d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/45160
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
Antonio Maiorano
2021-03-18 17:59:54 +00:00
committed by Commit Bot service account
parent e072465d83
commit 9970ec63ca
16 changed files with 497 additions and 322 deletions

View File

@@ -0,0 +1,205 @@
// 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/access_decoration.h"
#include "src/ast/constant_id_decoration.h"
#include "src/ast/stage_decoration.h"
#include "src/ast/struct_block_decoration.h"
#include "src/ast/workgroup_decoration.h"
#include "src/resolver/resolver.h"
#include "src/resolver/resolver_test_helper.h"
#include "gmock/gmock.h"
namespace tint {
namespace {
enum class DecorationKind {
kAccess,
kAlign,
kBinding,
kBuiltin,
kConstantId,
kGroup,
kLocation,
kOffset,
kSize,
kStage,
kStride,
kStructBlock,
kWorkgroup,
};
struct TestParams {
DecorationKind kind;
bool should_pass;
};
class TestWithParams : public resolver::TestHelper,
public testing::TestWithParam<TestParams> {};
ast::Decoration* createDecoration(const Source& source,
ProgramBuilder& builder,
DecorationKind kind) {
switch (kind) {
case DecorationKind::kAccess:
return builder.create<ast::AccessDecoration>(
source, ast::AccessControl::kReadOnly);
case DecorationKind::kAlign:
return builder.create<ast::StructMemberAlignDecoration>(source, 4u);
case DecorationKind::kBinding:
return builder.create<ast::BindingDecoration>(source, 1);
case DecorationKind::kBuiltin:
return builder.create<ast::BuiltinDecoration>(source,
ast::Builtin::kPosition);
case DecorationKind::kConstantId:
return builder.create<ast::ConstantIdDecoration>(source, 0u);
case DecorationKind::kGroup:
return builder.create<ast::GroupDecoration>(source, 1u);
case DecorationKind::kLocation:
return builder.create<ast::LocationDecoration>(source, 1);
case DecorationKind::kOffset:
return builder.create<ast::StructMemberOffsetDecoration>(source, 4u);
case DecorationKind::kSize:
return builder.create<ast::StructMemberSizeDecoration>(source, 4u);
case DecorationKind::kStage:
return builder.create<ast::StageDecoration>(source,
ast::PipelineStage::kCompute);
case DecorationKind::kStride:
return builder.create<ast::StrideDecoration>(source, 4u);
case DecorationKind::kStructBlock:
return builder.create<ast::StructBlockDecoration>(source);
case DecorationKind::kWorkgroup:
return builder.create<ast::WorkgroupDecoration>(source, 1u, 1u, 1u);
}
return nullptr;
}
using ArrayDecorationTest = TestWithParams;
TEST_P(ArrayDecorationTest, IsValid) {
auto params = GetParam();
ast::StructMemberList members{Member(
"a", create<type::Array>(ty.f32(), 0,
ast::DecorationList{createDecoration(
Source{{12, 34}}, *this, params.kind)}))};
auto* s = create<ast::Struct>(
members, ast::DecorationList{create<ast::StructBlockDecoration>()});
auto* s_ty = ty.struct_("mystruct", s);
AST().AddConstructedType(s_ty);
WrapInFunction();
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for array types");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
ArrayDecorationTest,
testing::Values(TestParams{DecorationKind::kAccess, false},
TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kConstantId, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, true},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
using StructDecorationTest = TestWithParams;
TEST_P(StructDecorationTest, IsValid) {
auto params = GetParam();
auto* s = create<ast::Struct>(ast::StructMemberList{},
ast::DecorationList{createDecoration(
Source{{12, 34}}, *this, params.kind)});
auto* s_ty = ty.struct_("mystruct", s);
AST().AddConstructedType(s_ty);
WrapInFunction();
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for struct declarations");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
StructDecorationTest,
testing::Values(TestParams{DecorationKind::kAccess, false},
TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kConstantId, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, true},
TestParams{DecorationKind::kWorkgroup, false}));
using StructMemberDecorationTest = TestWithParams;
TEST_P(StructMemberDecorationTest, IsValid) {
auto params = GetParam();
ast::StructMemberList members{
Member("a", ty.i32(),
ast::DecorationList{
createDecoration(Source{{12, 34}}, *this, params.kind)})};
auto* s = create<ast::Struct>(members, ast::DecorationList{});
auto* s_ty = ty.struct_("mystruct", s);
AST().AddConstructedType(s_ty);
WrapInFunction();
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for structure members");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
StructMemberDecorationTest,
testing::Values(TestParams{DecorationKind::kAccess, false},
TestParams{DecorationKind::kAlign, true},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true},
TestParams{DecorationKind::kConstantId, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kLocation, true},
TestParams{DecorationKind::kOffset, true},
TestParams{DecorationKind::kSize, true},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
} // namespace
} // namespace tint

View File

@@ -27,6 +27,7 @@
#include "src/ast/if_statement.h"
#include "src/ast/loop_statement.h"
#include "src/ast/return_statement.h"
#include "src/ast/struct_block_decoration.h"
#include "src/ast/switch_statement.h"
#include "src/ast/unary_op_expression.h"
#include "src/ast/variable_decl_statement.h"
@@ -1391,6 +1392,61 @@ const semantic::Array* Resolver::Array(type::Array* arr) {
return create_semantic(utils::RoundUp(el_align, el_size));
}
bool Resolver::ValidateStructure(const type::Struct* st) {
for (auto* member : st->impl()->members()) {
if (auto* r = member->type()->UnwrapAll()->As<type::Array>()) {
if (r->IsRuntimeArray()) {
if (member != st->impl()->members().back()) {
diagnostics_.add_error(
"v-0015",
"runtime arrays may only appear as the last member of a struct",
member->source());
return false;
}
if (!st->IsBlockDecorated()) {
diagnostics_.add_error("v-0015",
"a struct containing a runtime-sized array "
"requires the [[block]] attribute: '" +
builder_->Symbols().NameFor(st->symbol()) +
"'",
member->source());
return false;
}
for (auto* deco : r->decorations()) {
if (!deco->Is<ast::StrideDecoration>()) {
diagnostics_.add_error("decoration is not valid for array types",
deco->source());
return false;
}
}
}
}
for (auto* deco : member->decorations()) {
if (!(deco->Is<ast::BuiltinDecoration>() ||
deco->Is<ast::LocationDecoration>() ||
deco->Is<ast::StructMemberOffsetDecoration>() ||
deco->Is<ast::StructMemberSizeDecoration>() ||
deco->Is<ast::StructMemberAlignDecoration>())) {
diagnostics_.add_error("decoration is not valid for structure members",
deco->source());
return false;
}
}
}
for (auto* deco : st->impl()->decorations()) {
if (!(deco->Is<ast::StructBlockDecoration>())) {
diagnostics_.add_error("decoration is not valid for struct declarations",
deco->source());
return false;
}
}
return true;
}
Resolver::StructInfo* Resolver::Structure(type::Struct* str) {
auto info_it = struct_info_.find(str);
if (info_it != struct_info_.end()) {
@@ -1398,6 +1454,10 @@ Resolver::StructInfo* Resolver::Structure(type::Struct* str) {
return info_it->second;
}
if (!ValidateStructure(str)) {
return nullptr;
}
semantic::StructMemberList sem_members;
sem_members.reserve(str->impl()->members().size());

View File

@@ -216,6 +216,10 @@ class Resolver {
/// returned.
const semantic::Array* Array(type::Array*);
/// @returns returns true if input struct is valid
/// @param st the struct to validate
bool ValidateStructure(const type::Struct* st);
/// @returns the StructInfo for the structure `str`, building it if it hasn't
/// been constructed already. If an error is raised, nullptr is returned.
StructInfo* Structure(type::Struct* str);

View File

@@ -15,6 +15,7 @@
#include "src/resolver/resolver.h"
#include "gmock/gmock.h"
#include "src/ast/struct_block_decoration.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/semantic/struct.h"
@@ -125,9 +126,12 @@ TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
}
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
auto* s = Structure("S", {
Member("c", ty.array<f32>()),
});
auto* s =
Structure("S",
{
Member("c", ty.array<f32>()),
},
ast::DecorationList{create<ast::StructBlockDecoration>()});
ASSERT_TRUE(r()->Resolve()) << r()->error();
@@ -143,9 +147,12 @@ TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
}
TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
auto* s = Structure("S", {
Member("c", ty.array<f32>(/*stride*/ 32)),
});
auto* s =
Structure("S",
{
Member("c", ty.array<f32>(/*stride*/ 32)),
},
ast::DecorationList{create<ast::StructBlockDecoration>()});
ASSERT_TRUE(r()->Resolve()) << r()->error();

View File

@@ -0,0 +1,152 @@
// 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/stage_decoration.h"
#include "src/ast/struct_block_decoration.h"
#include "src/resolver/resolver.h"
#include "src/resolver/resolver_test_helper.h"
#include "gmock/gmock.h"
namespace tint {
namespace {
class ResolverTypeValidationTest : public resolver::TestHelper,
public testing::Test {};
TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLast_Pass) {
// [[Block]]
// struct Foo {
// vf: f32;
// rt: array<f32>;
// };
ast::DecorationList decos;
decos.push_back(create<ast::StructBlockDecoration>());
auto* st =
create<ast::Struct>(ast::StructMemberList{Member("vf", ty.f32()),
Member("rt", ty.array<f32>())},
decos);
auto* struct_type = ty.struct_("Foo", st);
AST().AddConstructedType(struct_type);
WrapInFunction();
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLastNoBlock_Fail) {
// struct Foo {
// vf: f32;
// rt: array<f32>;
// };
ast::DecorationList decos;
auto* st = create<ast::Struct>(
ast::StructMemberList{Member("vf", ty.f32()),
Member(Source{{12, 34}}, "rt", ty.array<f32>())},
decos);
auto* struct_type = ty.struct_("Foo", st);
AST().AddConstructedType(struct_type);
WrapInFunction();
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error v-0015: a struct containing a runtime-sized array "
"requires the [[block]] attribute: 'Foo'");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayIsNotLast_Fail) {
// [[Block]]
// struct Foo {
// rt: array<f32>;
// vf: f32;
// };
ast::DecorationList decos;
decos.push_back(create<ast::StructBlockDecoration>());
auto* rt = Member(Source{{12, 34}}, "rt", ty.array<f32>());
auto* st = create<ast::Struct>(
ast::StructMemberList{rt, Member("vf", ty.f32())}, decos);
auto* struct_type = ty.struct_("Foo", st);
AST().AddConstructedType(struct_type);
WrapInFunction();
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error v-0015: runtime arrays may only appear as the last "
"member of a struct");
}
TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
// [[Block]]
// type RTArr = array<u32>;
// struct s {
// b: RTArr;
// a: u32;
//}
auto* alias = ty.alias("RTArr", ty.array<u32>());
ast::DecorationList decos;
decos.push_back(create<ast::StructBlockDecoration>());
auto* st = create<ast::Struct>(
ast::StructMemberList{Member(Source{{12, 34}}, "b", alias),
Member("a", ty.u32())},
decos);
auto* struct_type = ty.struct_("s", st);
AST().AddConstructedType(struct_type);
WrapInFunction();
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(
r()->error(),
"12:34 error v-0015: runtime arrays may only appear as the last member "
"of a struct");
}
TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsLast_Pass) {
// [[Block]]
// type RTArr = array<u32>;
// struct s {
// a: u32;
// b: RTArr;
//}
auto* alias = ty.alias("RTArr", ty.array<u32>());
ast::DecorationList decos;
decos.push_back(create<ast::StructBlockDecoration>());
auto* st = create<ast::Struct>(
ast::StructMemberList{Member("a", ty.u32()), Member("b", alias)}, decos);
auto* struct_type = ty.struct_("s", st);
AST().AddConstructedType(struct_type);
WrapInFunction();
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
} // namespace
} // namespace tint