[resolver] Add entry point IO validation

Checks the following things:
- Non-struct entry point parameters must have pipeline IO attributes
- Non-struct entry point return type must have a pipeline IO attribute
- Structs used as entry point parameters and return values much have
  pipeline IO attributes on every member
- Structs used as entry point parameters and return values cannot have
  runtime array or nested struct members
- Multiple pipeline IO attributes on a parameter, return type, or
  struct member is not allowed
- Any given builtin and location attribute can only appear once for
  the return type, and across all parameters

Removed tests for nested structs from the SPIR-V transform/backend.

Fixed a couple of other tests with missing pipeline IO attributes.

Fixed: tint:512
Change-Id: I4c48fe24099333c8c7fcd45934c09baa6830883c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/46701
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
James Price
2021-04-06 15:51:47 +00:00
committed by Commit Bot service account
parent 09356cc3dc
commit 68f558f29c
10 changed files with 810 additions and 464 deletions

View File

@@ -0,0 +1,504 @@
// 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/builtin_decoration.h"
#include "src/ast/location_decoration.h"
#include "src/ast/return_statement.h"
#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 ResolverEntryPointValidationTest : public resolver::TestHelper,
public testing::Test {};
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Location) {
// [[stage(vertex)]]
// fn main() -> [[location(0)]] f32 { return 1.0; }
Func(Source{{12, 34}}, "main", {}, ty.f32(), {Return(Expr(1.0f))},
{Stage(ast::PipelineStage::kVertex)}, {Location(0)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Builtin) {
// [[stage(vertex)]]
// fn main() -> [[builtin(position)]] vec4<f32> { return vec4<f32>(); }
Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
{Return(Construct(ty.vec4<f32>()))},
{Stage(ast::PipelineStage::kVertex)},
{Builtin(ast::Builtin::kPosition)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Missing) {
// [[stage(vertex)]]
// fn main() -> f32 {
// return 1.0;
// }
Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
{Return(Construct(ty.vec4<f32>()))},
{Stage(ast::PipelineStage::kVertex)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: missing entry point IO attribute on return type");
}
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Multiple) {
// [[stage(vertex)]]
// fn main() -> [[location(0)]] [[builtin(position)]] vec4<f32> {
// return vec4<f32>();
// }
Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
{Return(Construct(ty.vec4<f32>()))},
{Stage(ast::PipelineStage::kVertex)},
{Location(Source{{13, 43}}, 0),
Builtin(Source{{14, 52}}, ast::Builtin::kPosition)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0))");
}
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Struct) {
// struct Output {
// };
// [[stage(vertex)]]
// fn main() -> [[location(0)]] Output {
// return Output();
// }
auto* output = Structure("Output", {});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kVertex)}, {Location(Source{{13, 43}}, 0)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"13:43 error: entry point IO attributes must not be used on structure "
"return types");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_Valid) {
// struct Output {
// [[location(0)]] a : f32;
// [[builtin(frag_depth)]] b : f32;
// };
// [[stage(fragment)]]
// fn main() -> Output {
// return Output();
// }
auto* output = Structure(
"Output", {Member("a", ty.f32(), {Location(0)}),
Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverEntryPointValidationTest,
ReturnType_Struct_MemberMultipleAttributes) {
// struct Output {
// [[location(0)]] [[builtin(frag_depth)]] a : f32;
// };
// [[stage(fragment)]]
// fn main() -> Output {
// return Output();
// }
auto* output = Structure(
"Output",
{Member("a", ty.f32(),
{Location(Source{{13, 43}}, 0),
Builtin(Source{{14, 52}}, ast::Builtin::kFragDepth)})});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0)
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest,
ReturnType_Struct_MemberMissingAttribute) {
// struct Output {
// [[location(0)]] a : f32;
// b : f32;
// };
// [[stage(fragment)]]
// fn main() -> Output {
// return Output();
// }
auto* output = Structure(
"Output", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
Member(Source{{14, 52}}, "b", ty.f32(), {})});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(14:52 error: missing entry point IO attribute
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_NestedStruct) {
// struct Inner {
// [[location(0)]] b : f32;
// };
// struct Output {
// [[location(0)]] a : Inner;
// };
// [[stage(fragment)]]
// fn main() -> Output {
// return Output();
// }
auto* inner = Structure(
"Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
auto* output = Structure(
"Output", {Member(Source{{14, 52}}, "a", inner, {Location(0)})});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(14:52 error: entry point IO types cannot contain nested structures
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_RuntimeArray) {
// [[block]]
// struct Output {
// [[location(0)]] a : array<f32>;
// };
// [[stage(fragment)]]
// fn main() -> Output {
// return Output();
// }
auto* output = Structure(
"Output",
{Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
{create<ast::StructBlockDecoration>()});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(13:43 error: entry point IO types cannot contain runtime sized arrays
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
// struct Output {
// [[builtin(frag_depth)]] a : f32;
// [[builtin(frag_depth)]] b : f32;
// };
// [[stage(fragment)]]
// fn main() -> Output {
// return Output();
// }
auto* output = Structure(
"Output", {Member("a", ty.f32(), {Builtin(ast::Builtin::kFragDepth)}),
Member("b", ty.f32(), {Builtin(ast::Builtin::kFragDepth)})});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: builtin(frag_depth) attribute appears multiple times as pipeline output
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateLocation) {
// struct Output {
// [[location(1)]] a : f32;
// [[location(1)]] b : f32;
// };
// [[stage(fragment)]]
// fn main() -> Output {
// return Output();
// }
auto* output = Structure("Output", {Member("a", ty.f32(), {Location(1)}),
Member("b", ty.f32(), {Location(1)})});
Func(Source{{12, 34}}, "main", {}, output, {Return(Construct(output))},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: location(1) attribute appears multiple times as pipeline output
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
// [[stage(fragment)]]
// fn main([[location(0)]] param : f32) {}
auto* param = Const("param", ty.f32(), nullptr, {Location(0)});
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Builtin) {
// [[stage(fragment)]]
// fn main([[builtin(frag_depth)]] param : f32) {}
auto* param = Const("param", ty.vec4<f32>(), nullptr,
{Builtin(ast::Builtin::kFragDepth)});
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Missing) {
// [[stage(fragment)]]
// fn main(param : f32) {}
auto* param = Const(Source{{13, 43}}, "param", ty.vec4<f32>(), nullptr);
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"13:43 error: missing entry point IO attribute on parameter");
}
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Multiple) {
// [[stage(fragment)]]
// fn main([[location(0)]] [[builtin(vertex_index)]] param : u32) {}
auto* param = Const("param", ty.u32(), nullptr,
{Location(Source{{13, 43}}, 0),
Builtin(Source{{14, 52}}, ast::Builtin::kVertexIndex)});
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0))");
}
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Struct) {
// struct Input {
// };
// [[stage(fragment)]]
// fn main([[location(0)]] param : Input) {}
auto* input = Structure("Input", {});
auto* param = Const("param", input, nullptr, {Location(Source{{13, 43}}, 0)});
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"13:43 error: entry point IO attributes must not be used on structure "
"parameters");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_Valid) {
// struct Input {
// [[location(0)]] a : f32;
// [[builtin(sample_index)]] b : u32;
// };
// [[stage(fragment)]]
// fn main(param : Input) {}
auto* input = Structure(
"Input", {Member("a", ty.f32(), {Location(0)}),
Member("b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
auto* param = Const("param", input);
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverEntryPointValidationTest,
Parameter_Struct_MemberMultipleAttributes) {
// struct Input {
// [[location(0)]] [[builtin(sample_index)]] a : u32;
// };
// [[stage(fragment)]]
// fn main(param : Input) {}
auto* input = Structure(
"Input",
{Member("a", ty.f32(),
{Location(Source{{13, 43}}, 0),
Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})});
auto* param = Const("param", input);
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0)
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest,
Parameter_Struct_MemberMissingAttribute) {
// struct Input {
// [[location(0)]] a : f32;
// b : f32;
// };
// [[stage(fragment)]]
// fn main(param : Input) {}
auto* input = Structure(
"Input", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
Member(Source{{14, 52}}, "b", ty.f32(), {})});
auto* param = Const("param", input);
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: missing entry point IO attribute
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_NestedStruct) {
// struct Inner {
// [[location(0)]] b : f32;
// };
// struct Input {
// [[location(0)]] a : Inner;
// };
// [[stage(fragment)]]
// fn main(param : Input) {}
auto* inner = Structure(
"Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
auto* input =
Structure("Input", {Member(Source{{14, 52}}, "a", inner, {Location(0)})});
auto* param = Const("param", input);
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(14:52 error: entry point IO types cannot contain nested structures
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_RuntimeArray) {
// [[block]]
// struct Input {
// [[location(0)]] a : array<f32>;
// };
// [[stage(fragment)]]
// fn main(param : Input) {}
auto* input = Structure(
"Input",
{Member(Source{{13, 43}}, "a", ty.array<float>(), {Location(0)})},
{create<ast::StructBlockDecoration>()});
auto* param = Const("param", input);
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(13:43 error: entry point IO types cannot contain runtime sized arrays
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateBuiltins) {
// [[stage(fragment)]]
// fn main([[builtin(sample_index)]] param_a : u32,
// [[builtin(sample_index)]] param_b : u32) {}
auto* param_a = Const("param_a", ty.u32(), nullptr,
{Builtin(ast::Builtin::kSampleIndex)});
auto* param_b = Const("param_b", ty.u32(), nullptr,
{Builtin(ast::Builtin::kSampleIndex)});
Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: builtin(sample_index) attribute appears multiple times as "
"pipeline input");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateBuiltins) {
// struct InputA {
// [[builtin(sample_index)]] a : u32;
// };
// struct InputB {
// [[builtin(sample_index)]] a : u32;
// };
// [[stage(fragment)]]
// fn main(param_a : InputA, param_b : InputB) {}
auto* input_a = Structure(
"InputA", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
auto* input_b = Structure(
"InputB", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
auto* param_a = Const("param_a", input_a);
auto* param_b = Const("param_b", input_b);
Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: builtin(sample_index) attribute appears multiple times as pipeline input
12:34 note: while analysing entry point main)");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateLocation) {
// [[stage(fragment)]]
// fn main([[location(1)]] param_a : f32,
// [[location(1)]] param_b : f32) {}
auto* param_a = Const("param_a", ty.u32(), nullptr, {Location(1)});
auto* param_b = Const("param_b", ty.u32(), nullptr, {Location(1)});
Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: location(1) attribute appears multiple times as "
"pipeline input");
}
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateLocation) {
// struct InputA {
// [[location(1)]] a : f32;
// };
// struct InputB {
// [[location(1)]] a : f32;
// };
// [[stage(fragment)]]
// fn main(param_a : InputA, param_b : InputB) {}
auto* input_a = Structure("InputA", {Member("a", ty.f32(), {Location(1)})});
auto* input_b = Structure("InputB", {Member("a", ty.f32(), {Location(1)})});
auto* param_a = Const("param_a", input_a);
auto* param_b = Const("param_b", input_b);
Func(Source{{12, 34}}, "main", {param_a, param_b}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(12:34 error: location(1) attribute appears multiple times as pipeline input
12:34 note: while analysing entry point main)");
}
} // namespace
} // namespace tint

View File

@@ -268,8 +268,7 @@ bool Resolver::ValidateFunction(const ast::Function* func) {
}
for (auto* deco : func->return_type_decorations()) {
if (!(deco->Is<ast::BuiltinDecoration>() ||
deco->Is<ast::LocationDecoration>())) {
if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::LocationDecoration>()) {
diagnostics_.add_error(
"decoration is not valid for function return types",
deco->source());
@@ -278,6 +277,186 @@ bool Resolver::ValidateFunction(const ast::Function* func) {
}
}
if (func->IsEntryPoint()) {
if (!ValidateEntryPoint(func)) {
return false;
}
}
return true;
}
bool Resolver::ValidateEntryPoint(const ast::Function* func) {
// Use a lambda to validate the entry point decorations for a type.
// Persistent state is used to track which builtins and locations have already
// been seen, in order to catch conflicts.
// TODO(jrprice): This state could be stored in FunctionInfo instead, and then
// passed to semantic::Function since it would be useful there too.
std::unordered_set<ast::Builtin> builtins;
std::unordered_set<uint32_t> locations;
enum class ParamOrRetType {
kParameter,
kReturnType,
};
// Helper to stringify a pipeline IO decoration.
auto deco_to_str = [](const ast::Decoration* deco) {
std::stringstream str;
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
str << "builtin(" << builtin->value() << ")";
} else if (auto* location = deco->As<ast::LocationDecoration>()) {
str << "location(" << location->value() << ")";
}
return str.str();
};
// Inner lambda that is applied to a type and all of its members.
auto validate_entry_point_decorations_inner =
[&](const ast::DecorationList& decos, type::Type* ty, Source source,
ParamOrRetType param_or_ret, bool is_struct_member) {
// Scan decorations for pipeline IO attributes.
// Check for overlap with attributes that have been seen previously.
ast::Decoration* pipeline_io_attribute = nullptr;
for (auto* deco : decos) {
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
if (pipeline_io_attribute) {
diagnostics_.add_error("multiple entry point IO attributes",
deco->source());
diagnostics_.add_note(
"previously consumed " + deco_to_str(pipeline_io_attribute),
pipeline_io_attribute->source());
return false;
}
pipeline_io_attribute = deco;
if (builtins.count(builtin->value())) {
diagnostics_.add_error(
deco_to_str(builtin) +
" attribute appears multiple times as pipeline " +
(param_or_ret == ParamOrRetType::kParameter ? "input"
: "output"),
func->source());
return false;
}
builtins.emplace(builtin->value());
} else if (auto* location = deco->As<ast::LocationDecoration>()) {
if (pipeline_io_attribute) {
diagnostics_.add_error("multiple entry point IO attributes",
deco->source());
diagnostics_.add_note(
"previously consumed " + deco_to_str(pipeline_io_attribute),
pipeline_io_attribute->source());
return false;
}
pipeline_io_attribute = deco;
if (locations.count(location->value())) {
diagnostics_.add_error(
deco_to_str(location) +
" attribute appears multiple times as pipeline " +
(param_or_ret == ParamOrRetType::kParameter ? "input"
: "output"),
func->source());
return false;
}
locations.emplace(location->value());
}
}
// Check that we saw a pipeline IO attribute iff we need one.
if (ty->UnwrapAliasIfNeeded()->Is<type::Struct>()) {
if (pipeline_io_attribute) {
diagnostics_.add_error(
"entry point IO attributes must not be used on structure " +
std::string(param_or_ret == ParamOrRetType::kParameter
? "parameters"
: "return types"),
pipeline_io_attribute->source());
return false;
}
} else {
if (!pipeline_io_attribute) {
std::string err = "missing entry point IO attribute";
if (!is_struct_member) {
err += (param_or_ret == ParamOrRetType::kParameter
? " on parameter"
: " on return type");
}
diagnostics_.add_error(err, source);
return false;
}
}
return true;
};
// Outer lambda for validating the entry point decorations for a type.
auto validate_entry_point_decorations = [&](const ast::DecorationList& decos,
type::Type* ty, Source source,
ParamOrRetType param_or_ret) {
// Validate the decorations for the type.
if (!validate_entry_point_decorations_inner(decos, ty, source, param_or_ret,
false)) {
return false;
}
if (auto* struct_ty = ty->UnwrapAliasIfNeeded()->As<type::Struct>()) {
// Validate the decorations for each struct members, and also check for
// invalid member types.
for (auto* member : struct_ty->impl()->members()) {
auto* member_ty = member->type()->UnwrapAliasIfNeeded();
if (member_ty->Is<type::Struct>()) {
diagnostics_.add_error(
"entry point IO types cannot contain nested structures",
member->source());
diagnostics_.add_note("while analysing entry point " +
builder_->Symbols().NameFor(func->symbol()),
func->source());
return false;
} else if (auto* arr = member_ty->As<type::Array>()) {
if (arr->IsRuntimeArray()) {
diagnostics_.add_error(
"entry point IO types cannot contain runtime sized arrays",
member->source());
diagnostics_.add_note(
"while analysing entry point " +
builder_->Symbols().NameFor(func->symbol()),
func->source());
return false;
}
}
if (!validate_entry_point_decorations_inner(member->decorations(),
member_ty, member->source(),
param_or_ret, true)) {
diagnostics_.add_note("while analysing entry point " +
builder_->Symbols().NameFor(func->symbol()),
func->source());
return false;
}
}
}
return true;
};
for (auto* param : func->params()) {
if (!validate_entry_point_decorations(
param->decorations(), param->declared_type(), param->source(),
ParamOrRetType::kParameter)) {
return false;
}
}
if (!func->return_type()->Is<type::Void>()) {
builtins.clear();
locations.clear();
if (!validate_entry_point_decorations(func->return_type_decorations(),
func->return_type(), func->source(),
ParamOrRetType::kReturnType)) {
return false;
}
}
return true;
}

View File

@@ -234,6 +234,7 @@ class Resolver {
bool ValidateVariable(const ast::Variable* param);
bool ValidateParameter(const ast::Variable* param);
bool ValidateFunction(const ast::Function* func);
bool ValidateEntryPoint(const ast::Function* func);
bool ValidateStructure(const type::Struct* st);
bool ValidateReturn(const ast::ReturnStatement* ret);
bool ValidateSwitch(const ast::SwitchStatement* s);