// 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/return_statement.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 {}; ast::Decoration* createDecoration(const Source& source, ProgramBuilder& builder, DecorationKind kind) { switch (kind) { case DecorationKind::kAccess: return builder.create( source, ast::AccessControl::kReadOnly); case DecorationKind::kAlign: return builder.create(source, 4u); case DecorationKind::kBinding: return builder.create(source, 1); case DecorationKind::kBuiltin: return builder.create(source, ast::Builtin::kPosition); case DecorationKind::kConstantId: return builder.create(source, 0u); case DecorationKind::kGroup: return builder.create(source, 1u); case DecorationKind::kLocation: return builder.create(source, 1); case DecorationKind::kOffset: return builder.create(source, 4u); case DecorationKind::kSize: return builder.create(source, 4u); case DecorationKind::kStage: return builder.create(source, ast::PipelineStage::kCompute); case DecorationKind::kStride: return builder.create(source, 4u); case DecorationKind::kStructBlock: return builder.create(source); case DecorationKind::kWorkgroup: return builder.create(source, 1u, 1u, 1u); } return nullptr; } using FunctionReturnTypeDecorationTest = TestWithParams; TEST_P(FunctionReturnTypeDecorationTest, IsValid) { auto params = GetParam(); Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{create(Expr(1.f))}, ast::DecorationList{ create(ast::PipelineStage::kVertex)}, ast::DecorationList{createDecoration({}, *this, params.kind)}); if (params.should_pass) { EXPECT_TRUE(r()->Resolve()) << r()->error(); } else { EXPECT_FALSE(r()->Resolve()) << r()->error(); EXPECT_EQ(r()->error(), "error: decoration is not valid for function return types"); } } INSTANTIATE_TEST_SUITE_P( ResolverDecorationValidationTest, FunctionReturnTypeDecorationTest, testing::Values(TestParams{DecorationKind::kAccess, false}, TestParams{DecorationKind::kAlign, false}, TestParams{DecorationKind::kBinding, false}, TestParams{DecorationKind::kBuiltin, true}, TestParams{DecorationKind::kConstantId, false}, TestParams{DecorationKind::kGroup, false}, TestParams{DecorationKind::kLocation, true}, TestParams{DecorationKind::kOffset, false}, TestParams{DecorationKind::kSize, false}, TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kWorkgroup, false})); using ArrayDecorationTest = TestWithParams; TEST_P(ArrayDecorationTest, IsValid) { auto params = GetParam(); ast::StructMemberList members{Member( "a", create(ty.f32(), 0, ast::DecorationList{createDecoration( Source{{12, 34}}, *this, params.kind)}))}; auto* s = create( members, ast::DecorationList{create()}); 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::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(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})); using VariableDecorationTest = TestWithParams; TEST_P(VariableDecorationTest, IsValid) { auto params = GetParam(); Global("a", ty.f32(), ast::StorageClass::kInput, nullptr, ast::DecorationList{ createDecoration(Source{{12, 34}}, *this, params.kind)}); 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 variables"); } } INSTANTIATE_TEST_SUITE_P( ValidatorTest, VariableDecorationTest, testing::Values(TestParams{DecorationKind::kAccess, false}, TestParams{DecorationKind::kAlign, false}, TestParams{DecorationKind::kBinding, true}, TestParams{DecorationKind::kBuiltin, true}, TestParams{DecorationKind::kConstantId, true}, TestParams{DecorationKind::kGroup, true}, TestParams{DecorationKind::kLocation, true}, TestParams{DecorationKind::kOffset, false}, TestParams{DecorationKind::kSize, false}, TestParams{DecorationKind::kStage, false}, TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kWorkgroup, false})); using FunctionDecorationTest = TestWithParams; TEST_P(FunctionDecorationTest, IsValid) { auto params = GetParam(); Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, ast::DecorationList{ create(ast::PipelineStage::kCompute), createDecoration(Source{{12, 34}}, *this, params.kind)}); 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 functions"); } } INSTANTIATE_TEST_SUITE_P( ValidatorTest, FunctionDecorationTest, 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}, // Skip kStage as we always apply it in this test TestParams{DecorationKind::kStride, false}, TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kWorkgroup, true})); } // namespace } // namespace tint