[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:
parent
09356cc3dc
commit
68f558f29c
|
@ -476,6 +476,7 @@ if(${TINT_BUILD_TESTS})
|
|||
resolver/builtins_validation_test.cc
|
||||
resolver/control_block_validation_test.cc
|
||||
resolver/decoration_validation_test.cc
|
||||
resolver/entry_point_validation_test.cc
|
||||
resolver/function_validation_test.cc
|
||||
resolver/host_shareable_validation_test.cc
|
||||
resolver/intrinsic_test.cc
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "src/ast/return_statement.h"
|
||||
#include "src/ast/scalar_constructor_expression.h"
|
||||
#include "src/ast/sint_literal.h"
|
||||
#include "src/ast/stage_decoration.h"
|
||||
#include "src/ast/stride_decoration.h"
|
||||
#include "src/ast/struct_member_align_decoration.h"
|
||||
#include "src/ast/struct_member_offset_decoration.h"
|
||||
|
@ -1246,6 +1247,51 @@ class ProgramBuilder {
|
|||
return Case(ast::CaseSelectorList{}, body);
|
||||
}
|
||||
|
||||
/// Creates an ast::BuiltinDecoration
|
||||
/// @param source the source information
|
||||
/// @param builtin the builtin value
|
||||
/// @returns the builtin decoration pointer
|
||||
ast::BuiltinDecoration* Builtin(Source source, ast::Builtin builtin) {
|
||||
return create<ast::BuiltinDecoration>(source, builtin);
|
||||
}
|
||||
|
||||
/// Creates an ast::BuiltinDecoration
|
||||
/// @param builtin the builtin value
|
||||
/// @returns the builtin decoration pointer
|
||||
ast::BuiltinDecoration* Builtin(ast::Builtin builtin) {
|
||||
return create<ast::BuiltinDecoration>(source_, builtin);
|
||||
}
|
||||
|
||||
/// Creates an ast::LocationDecoration
|
||||
/// @param source the source information
|
||||
/// @param location the location value
|
||||
/// @returns the location decoration pointer
|
||||
ast::LocationDecoration* Location(Source source, uint32_t location) {
|
||||
return create<ast::LocationDecoration>(source, location);
|
||||
}
|
||||
|
||||
/// Creates an ast::LocationDecoration
|
||||
/// @param location the location value
|
||||
/// @returns the location decoration pointer
|
||||
ast::LocationDecoration* Location(uint32_t location) {
|
||||
return create<ast::LocationDecoration>(source_, location);
|
||||
}
|
||||
|
||||
/// Creates an ast::StageDecoration
|
||||
/// @param source the source information
|
||||
/// @param stage the pipeline stage
|
||||
/// @returns the stage decoration pointer
|
||||
ast::StageDecoration* Stage(Source source, ast::PipelineStage stage) {
|
||||
return create<ast::StageDecoration>(source, stage);
|
||||
}
|
||||
|
||||
/// Creates an ast::StageDecoration
|
||||
/// @param stage the pipeline stage
|
||||
/// @returns the stage decoration pointer
|
||||
ast::StageDecoration* Stage(ast::PipelineStage stage) {
|
||||
return create<ast::StageDecoration>(source_, stage);
|
||||
}
|
||||
|
||||
/// Sets the current builder source to `src`
|
||||
/// @param src the Source used for future create() calls
|
||||
void SetSource(const Source& src) {
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -83,7 +83,7 @@ fn _tint_3() -> void {
|
|||
TEST_F(RenamerTest, PreserveSwizzles) {
|
||||
auto* src = R"(
|
||||
[[stage(vertex)]]
|
||||
fn entry() -> vec4<f32> {
|
||||
fn entry() -> [[builtin(position)]] vec4<f32> {
|
||||
var v : vec4<f32>;
|
||||
var rgba : f32;
|
||||
var xyzw : f32;
|
||||
|
@ -93,7 +93,7 @@ fn entry() -> vec4<f32> {
|
|||
|
||||
auto* expect = R"(
|
||||
[[stage(vertex)]]
|
||||
fn _tint_1() -> vec4<f32> {
|
||||
fn _tint_1() -> [[builtin(position)]] vec4<f32> {
|
||||
var _tint_2 : vec4<f32>;
|
||||
var _tint_3 : f32;
|
||||
var _tint_4 : f32;
|
||||
|
@ -120,7 +120,7 @@ fn _tint_1() -> vec4<f32> {
|
|||
TEST_F(RenamerTest, PreserveIntrinsics) {
|
||||
auto* src = R"(
|
||||
[[stage(vertex)]]
|
||||
fn entry() -> vec4<f32> {
|
||||
fn entry() -> [[builtin(position)]] vec4<f32> {
|
||||
var blah : vec4<f32>;
|
||||
return abs(blah);
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ fn entry() -> vec4<f32> {
|
|||
|
||||
auto* expect = R"(
|
||||
[[stage(vertex)]]
|
||||
fn _tint_1() -> vec4<f32> {
|
||||
fn _tint_1() -> [[builtin(position)]] vec4<f32> {
|
||||
var _tint_2 : vec4<f32>;
|
||||
return abs(_tint_2);
|
||||
}
|
||||
|
|
|
@ -224,86 +224,10 @@ fn frag_main() -> void {
|
|||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(SpirvTest, HandleEntryPointIOTypes_StructParameters_Nested) {
|
||||
auto* src = R"(
|
||||
struct Builtins {
|
||||
[[builtin(frag_coord)]] coord : vec4<f32>;
|
||||
};
|
||||
|
||||
struct Locations {
|
||||
[[location(2)]] l2 : f32;
|
||||
[[location(3)]] l3 : f32;
|
||||
};
|
||||
|
||||
struct Other {
|
||||
l : Locations;
|
||||
};
|
||||
|
||||
struct FragmentInput {
|
||||
b : Builtins;
|
||||
o : Other;
|
||||
[[location(1)]] value : f32;
|
||||
};
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn frag_main(inputs : FragmentInput) -> void {
|
||||
var col : f32 = inputs.b.coord.x * inputs.value;
|
||||
var l : f32 = inputs.o.l.l2 + inputs.o.l.l3;
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
struct Builtins {
|
||||
coord : vec4<f32>;
|
||||
};
|
||||
|
||||
struct Locations {
|
||||
l2 : f32;
|
||||
l3 : f32;
|
||||
};
|
||||
|
||||
struct Other {
|
||||
l : Locations;
|
||||
};
|
||||
|
||||
struct FragmentInput {
|
||||
b : Builtins;
|
||||
o : Other;
|
||||
value : f32;
|
||||
};
|
||||
|
||||
[[builtin(frag_coord)]] var<in> tint_symbol_12 : vec4<f32>;
|
||||
|
||||
[[location(2)]] var<in> tint_symbol_14 : f32;
|
||||
|
||||
[[location(3)]] var<in> tint_symbol_15 : f32;
|
||||
|
||||
[[location(1)]] var<in> tint_symbol_18 : f32;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn frag_main() -> void {
|
||||
const tint_symbol_13 : Builtins = Builtins(tint_symbol_12);
|
||||
const tint_symbol_16 : Locations = Locations(tint_symbol_14, tint_symbol_15);
|
||||
const tint_symbol_17 : Other = Other(tint_symbol_16);
|
||||
const tint_symbol_19 : FragmentInput = FragmentInput(tint_symbol_13, tint_symbol_17, tint_symbol_18);
|
||||
var col : f32 = (tint_symbol_19.b.coord.x * tint_symbol_19.value);
|
||||
var l : f32 = (tint_symbol_19.o.l.l2 + tint_symbol_19.o.l.l3);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<Spirv>(src);
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(SpirvTest, HandleEntryPointIOTypes_StructParameters_EmptyBody) {
|
||||
auto* src = R"(
|
||||
struct Locations {
|
||||
[[location(1)]] value : f32;
|
||||
};
|
||||
|
||||
struct FragmentInput {
|
||||
locations : Locations;
|
||||
[[location(1)]] value : f32;
|
||||
};
|
||||
|
||||
[[stage(fragment)]]
|
||||
|
@ -312,15 +236,11 @@ fn frag_main(inputs : FragmentInput) -> void {
|
|||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
struct Locations {
|
||||
struct FragmentInput {
|
||||
value : f32;
|
||||
};
|
||||
|
||||
struct FragmentInput {
|
||||
locations : Locations;
|
||||
};
|
||||
|
||||
[[location(1)]] var<in> tint_symbol_5 : f32;
|
||||
[[location(1)]] var<in> tint_symbol_3 : f32;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn frag_main() -> void {
|
||||
|
@ -381,97 +301,6 @@ fn vert_main() -> void {
|
|||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(SpirvTest, HandleEntryPointIOTypes_ReturnStruct_Nested) {
|
||||
auto* src = R"(
|
||||
struct Builtins {
|
||||
[[builtin(position)]] pos : vec4<f32>;
|
||||
};
|
||||
|
||||
struct Locations {
|
||||
[[location(2)]] l2 : f32;
|
||||
[[location(3)]] l3 : f32;
|
||||
};
|
||||
|
||||
struct Other {
|
||||
l : Locations;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
b : Builtins;
|
||||
o : Other;
|
||||
[[location(1)]] value : f32;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vert_main() -> VertexOutput {
|
||||
if (false) {
|
||||
return VertexOutput();
|
||||
}
|
||||
var output : VertexOutput = VertexOutput();
|
||||
output.b.pos = vec4<f32>(1.0, 2.0, 3.0, 0.0);
|
||||
output.o.l.l2 = 4.0;
|
||||
output.o.l.l3 = 5.0;
|
||||
output.value = 6.0;
|
||||
return output;
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
struct Builtins {
|
||||
pos : vec4<f32>;
|
||||
};
|
||||
|
||||
struct Locations {
|
||||
l2 : f32;
|
||||
l3 : f32;
|
||||
};
|
||||
|
||||
struct Other {
|
||||
l : Locations;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
b : Builtins;
|
||||
o : Other;
|
||||
value : f32;
|
||||
};
|
||||
|
||||
[[builtin(position)]] var<out> tint_symbol_13 : vec4<f32>;
|
||||
|
||||
[[location(2)]] var<out> tint_symbol_14 : f32;
|
||||
|
||||
[[location(3)]] var<out> tint_symbol_15 : f32;
|
||||
|
||||
[[location(1)]] var<out> tint_symbol_16 : f32;
|
||||
|
||||
fn tint_symbol_17(tint_symbol_12 : VertexOutput) -> void {
|
||||
tint_symbol_13 = tint_symbol_12.b.pos;
|
||||
tint_symbol_14 = tint_symbol_12.o.l.l2;
|
||||
tint_symbol_15 = tint_symbol_12.o.l.l3;
|
||||
tint_symbol_16 = tint_symbol_12.value;
|
||||
}
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vert_main() -> void {
|
||||
if (false) {
|
||||
tint_symbol_17(VertexOutput());
|
||||
return;
|
||||
}
|
||||
var output : VertexOutput = VertexOutput();
|
||||
output.b.pos = vec4<f32>(1.0, 2.0, 3.0, 0.0);
|
||||
output.o.l.l2 = 4.0;
|
||||
output.o.l.l3 = 5.0;
|
||||
output.value = 6.0;
|
||||
tint_symbol_17(output);
|
||||
return;
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<Spirv>(src);
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(SpirvTest, HandleEntryPointIOTypes_SharedStruct_SameShader) {
|
||||
auto* src = R"(
|
||||
struct Interface {
|
||||
|
@ -558,150 +387,6 @@ fn frag_main() -> void {
|
|||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(SpirvTest, HandleEntryPointIOTypes_SharedSubStruct) {
|
||||
auto* src = R"(
|
||||
struct Interface {
|
||||
[[location(1)]] value : f32;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] pos : vec4<f32>;
|
||||
interface : Interface;
|
||||
};
|
||||
|
||||
struct FragmentInput {
|
||||
[[builtin(sample_index)]] index : u32;
|
||||
interface : Interface;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vert_main() -> VertexOutput {
|
||||
return VertexOutput(vec4<f32>(), Interface(42.0));
|
||||
}
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn frag_main(inputs : FragmentInput) -> void {
|
||||
var x : f32 = inputs.interface.value;
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
struct Interface {
|
||||
value : f32;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
pos : vec4<f32>;
|
||||
interface : Interface;
|
||||
};
|
||||
|
||||
struct FragmentInput {
|
||||
index : u32;
|
||||
interface : Interface;
|
||||
};
|
||||
|
||||
[[builtin(position)]] var<out> tint_symbol_9 : vec4<f32>;
|
||||
|
||||
[[location(1)]] var<out> tint_symbol_10 : f32;
|
||||
|
||||
fn tint_symbol_11(tint_symbol_8 : VertexOutput) -> void {
|
||||
tint_symbol_9 = tint_symbol_8.pos;
|
||||
tint_symbol_10 = tint_symbol_8.interface.value;
|
||||
}
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vert_main() -> void {
|
||||
tint_symbol_11(VertexOutput(vec4<f32>(), Interface(42.0)));
|
||||
return;
|
||||
}
|
||||
|
||||
[[builtin(sample_index)]] var<in> tint_symbol_13 : u32;
|
||||
|
||||
[[location(1)]] var<in> tint_symbol_14 : f32;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn frag_main() -> void {
|
||||
const tint_symbol_15 : Interface = Interface(tint_symbol_14);
|
||||
const tint_symbol_16 : FragmentInput = FragmentInput(tint_symbol_13, tint_symbol_15);
|
||||
var x : f32 = tint_symbol_16.interface.value;
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<Spirv>(src);
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(SpirvTest, HandleEntryPointIOTypes_NestedStruct_TypeAlias) {
|
||||
auto* src = R"(
|
||||
type myf32 = f32;
|
||||
|
||||
struct Location {
|
||||
[[location(2)]] l2 : myf32;
|
||||
};
|
||||
|
||||
type MyLocation = Location;
|
||||
|
||||
struct VertexIO {
|
||||
l : MyLocation;
|
||||
[[location(1)]] value : myf32;
|
||||
};
|
||||
|
||||
type MyVertexInput = VertexIO;
|
||||
|
||||
type MyVertexOutput = VertexIO;
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vert_main(inputs : MyVertexInput) -> MyVertexOutput {
|
||||
return inputs;
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
type myf32 = f32;
|
||||
|
||||
struct Location {
|
||||
l2 : myf32;
|
||||
};
|
||||
|
||||
type MyLocation = Location;
|
||||
|
||||
struct VertexIO {
|
||||
l : MyLocation;
|
||||
value : myf32;
|
||||
};
|
||||
|
||||
type MyVertexInput = VertexIO;
|
||||
|
||||
type MyVertexOutput = VertexIO;
|
||||
|
||||
[[location(2)]] var<in> tint_symbol_8 : myf32;
|
||||
|
||||
[[location(1)]] var<in> tint_symbol_10 : myf32;
|
||||
|
||||
[[location(2)]] var<out> tint_symbol_14 : myf32;
|
||||
|
||||
[[location(1)]] var<out> tint_symbol_15 : myf32;
|
||||
|
||||
fn tint_symbol_17(tint_symbol_13 : MyVertexOutput) -> void {
|
||||
tint_symbol_14 = tint_symbol_13.l.l2;
|
||||
tint_symbol_15 = tint_symbol_13.value;
|
||||
}
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vert_main() -> void {
|
||||
const tint_symbol_9 : MyLocation = MyLocation(tint_symbol_8);
|
||||
const tint_symbol_11 : MyVertexInput = MyVertexInput(tint_symbol_9, tint_symbol_10);
|
||||
tint_symbol_17(tint_symbol_11);
|
||||
return;
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<Spirv>(src);
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(SpirvTest, HandleEntryPointIOTypes_StructLayoutDecorations) {
|
||||
auto* src = R"(
|
||||
[[block]]
|
||||
|
|
|
@ -99,9 +99,7 @@ TEST_F(ValidateFunctionTest, FunctionVarInitWithParam) {
|
|||
auto* baz = Var("baz", ty.f32(), ast::StorageClass::kFunction, Expr("bar"));
|
||||
|
||||
Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
|
||||
ast::DecorationList{
|
||||
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
|
||||
});
|
||||
ast::DecorationList{});
|
||||
|
||||
ValidatorImpl& v = Build();
|
||||
|
||||
|
@ -117,9 +115,7 @@ TEST_F(ValidateFunctionTest, FunctionConstInitWithParam) {
|
|||
auto* baz = Const("baz", ty.f32(), Expr("bar"));
|
||||
|
||||
Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
|
||||
ast::DecorationList{
|
||||
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
|
||||
});
|
||||
ast::DecorationList{});
|
||||
|
||||
ValidatorImpl& v = Build();
|
||||
|
||||
|
|
|
@ -178,74 +178,37 @@ OpFunctionEnd
|
|||
Validate(b);
|
||||
}
|
||||
|
||||
TEST_F(BuilderTest, EntryPoint_SharedSubStruct) {
|
||||
TEST_F(BuilderTest, EntryPoint_SharedStruct) {
|
||||
// struct Interface {
|
||||
// [[location(1)]] value : f32;
|
||||
// };
|
||||
//
|
||||
// struct VertexOutput {
|
||||
// [[builtin(position)]] pos : vec4<f32>;
|
||||
// interface : Interface;
|
||||
// };
|
||||
//
|
||||
// struct FragmentInput {
|
||||
// [[location(0)]] mul : f32;
|
||||
// interface : Interface;
|
||||
// };
|
||||
//
|
||||
// [[stage(vertex)]]
|
||||
// fn vert_main() -> VertexOutput {
|
||||
// return VertexOutput(vec4<f32>(), Interface(42.0));
|
||||
// fn vert_main() -> Interface {
|
||||
// return Interface(42.0);
|
||||
// }
|
||||
//
|
||||
// [[stage(fragment)]]
|
||||
// fn frag_main(inputs : FragmentInput) -> [[builtin(frag_depth)]] f32 {
|
||||
// return inputs.mul * inputs.interface.value;
|
||||
// fn frag_main(inputs : Interface) -> [[builtin(frag_depth)]] f32 {
|
||||
// return inputs.value;
|
||||
// }
|
||||
|
||||
auto* interface =
|
||||
Structure("Interface",
|
||||
ast::StructMemberList{Member(
|
||||
"value", ty.f32(),
|
||||
ast::DecorationList{create<ast::LocationDecoration>(1u)})});
|
||||
auto* vertex_output = Structure(
|
||||
"VertexOutput",
|
||||
ast::StructMemberList{
|
||||
Member("pos", ty.vec4<f32>(),
|
||||
ast::DecorationList{
|
||||
create<ast::BuiltinDecoration>(ast::Builtin::kPosition)}),
|
||||
Member("interface", interface)});
|
||||
auto* fragment_input = Structure(
|
||||
"FragmentInput",
|
||||
ast::StructMemberList{
|
||||
Member("mul", ty.f32(),
|
||||
ast::DecorationList{create<ast::LocationDecoration>(0u)}),
|
||||
Member("interface", interface)});
|
||||
auto* interface = Structure(
|
||||
"Interface",
|
||||
{Member("value", ty.f32(),
|
||||
ast::DecorationList{create<ast::LocationDecoration>(1u)})});
|
||||
|
||||
auto* vert_retval = Construct(vertex_output, Construct(ty.vec4<f32>()),
|
||||
Construct(interface, 42.f));
|
||||
Func("vert_main", ast::VariableList{}, vertex_output,
|
||||
ast::StatementList{
|
||||
create<ast::ReturnStatement>(vert_retval),
|
||||
},
|
||||
ast::DecorationList{
|
||||
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
|
||||
});
|
||||
auto* vert_retval = Construct(interface, 42.f);
|
||||
Func("vert_main", ast::VariableList{}, interface,
|
||||
{create<ast::ReturnStatement>(vert_retval)},
|
||||
{create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
|
||||
|
||||
auto* frag_retval =
|
||||
Mul(MemberAccessor(Expr("inputs"), "mul"),
|
||||
MemberAccessor(MemberAccessor(Expr("inputs"), "interface"), "value"));
|
||||
auto* frag_inputs =
|
||||
Var("inputs", fragment_input, ast::StorageClass::kFunction, nullptr);
|
||||
Var("inputs", interface, ast::StorageClass::kFunction, nullptr);
|
||||
Func("frag_main", ast::VariableList{frag_inputs}, ty.f32(),
|
||||
ast::StatementList{
|
||||
create<ast::ReturnStatement>(frag_retval),
|
||||
},
|
||||
ast::DecorationList{
|
||||
create<ast::StageDecoration>(ast::PipelineStage::kFragment),
|
||||
},
|
||||
ast::DecorationList{
|
||||
create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth)});
|
||||
{create<ast::ReturnStatement>(MemberAccessor(Expr("inputs"), "value"))},
|
||||
{create<ast::StageDecoration>(ast::PipelineStage::kFragment)},
|
||||
{create<ast::BuiltinDecoration>(ast::Builtin::kFragDepth)});
|
||||
|
||||
spirv::Builder& b = SanitizeAndBuild();
|
||||
|
||||
|
@ -253,93 +216,63 @@ TEST_F(BuilderTest, EntryPoint_SharedSubStruct) {
|
|||
|
||||
EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Vertex %24 "vert_main" %1 %6
|
||||
OpEntryPoint Fragment %34 "frag_main" %11 %9 %12
|
||||
OpExecutionMode %34 OriginUpperLeft
|
||||
OpExecutionMode %34 DepthReplacing
|
||||
OpName %1 "tint_symbol_9"
|
||||
OpName %6 "tint_symbol_10"
|
||||
OpName %9 "tint_symbol_13"
|
||||
OpName %11 "tint_symbol_14"
|
||||
OpName %12 "tint_symbol_18"
|
||||
OpName %15 "VertexOutput"
|
||||
OpMemberName %15 0 "pos"
|
||||
OpMemberName %15 1 "interface"
|
||||
OpName %16 "Interface"
|
||||
OpMemberName %16 0 "value"
|
||||
OpName %17 "tint_symbol_11"
|
||||
OpName %18 "tint_symbol_8"
|
||||
OpName %24 "vert_main"
|
||||
OpName %31 "tint_symbol_19"
|
||||
OpName %32 "tint_symbol_17"
|
||||
OpName %34 "frag_main"
|
||||
OpName %38 "FragmentInput"
|
||||
OpMemberName %38 0 "mul"
|
||||
OpMemberName %38 1 "interface"
|
||||
OpDecorate %1 BuiltIn Position
|
||||
OpDecorate %6 Location 1
|
||||
OpDecorate %9 Location 0
|
||||
OpDecorate %11 Location 1
|
||||
OpDecorate %12 BuiltIn FragDepth
|
||||
OpMemberDecorate %15 0 Offset 0
|
||||
OpMemberDecorate %15 1 Offset 16
|
||||
OpMemberDecorate %16 0 Offset 0
|
||||
OpMemberDecorate %38 0 Offset 0
|
||||
OpMemberDecorate %38 1 Offset 4
|
||||
%4 = OpTypeFloat 32
|
||||
%3 = OpTypeVector %4 4
|
||||
OpEntryPoint Vertex %16 "vert_main" %1
|
||||
OpEntryPoint Fragment %25 "frag_main" %5 %7
|
||||
OpExecutionMode %25 OriginUpperLeft
|
||||
OpExecutionMode %25 DepthReplacing
|
||||
OpName %1 "tint_symbol_4"
|
||||
OpName %5 "tint_symbol_7"
|
||||
OpName %7 "tint_symbol_10"
|
||||
OpName %10 "Interface"
|
||||
OpMemberName %10 0 "value"
|
||||
OpName %11 "tint_symbol_5"
|
||||
OpName %12 "tint_symbol_3"
|
||||
OpName %16 "vert_main"
|
||||
OpName %22 "tint_symbol_11"
|
||||
OpName %23 "tint_symbol_9"
|
||||
OpName %25 "frag_main"
|
||||
OpDecorate %1 Location 1
|
||||
OpDecorate %5 Location 1
|
||||
OpDecorate %7 BuiltIn FragDepth
|
||||
OpMemberDecorate %10 0 Offset 0
|
||||
%3 = OpTypeFloat 32
|
||||
%2 = OpTypePointer Output %3
|
||||
%5 = OpConstantNull %3
|
||||
%1 = OpVariable %2 Output %5
|
||||
%7 = OpTypePointer Output %4
|
||||
%8 = OpConstantNull %4
|
||||
%6 = OpVariable %7 Output %8
|
||||
%10 = OpTypePointer Input %4
|
||||
%9 = OpVariable %10 Input
|
||||
%11 = OpVariable %10 Input
|
||||
%12 = OpVariable %7 Output %8
|
||||
%14 = OpTypeVoid
|
||||
%16 = OpTypeStruct %4
|
||||
%15 = OpTypeStruct %3 %16
|
||||
%13 = OpTypeFunction %14 %15
|
||||
%23 = OpTypeFunction %14
|
||||
%27 = OpConstant %4 42
|
||||
%28 = OpConstantComposite %16 %27
|
||||
%29 = OpConstantComposite %15 %5 %28
|
||||
%30 = OpTypeFunction %14 %4
|
||||
%38 = OpTypeStruct %4 %16
|
||||
%17 = OpFunction %14 None %13
|
||||
%18 = OpFunctionParameter %15
|
||||
%19 = OpLabel
|
||||
%20 = OpCompositeExtract %3 %18 0
|
||||
OpStore %1 %20
|
||||
%21 = OpCompositeExtract %16 %18 1
|
||||
%22 = OpCompositeExtract %4 %21 0
|
||||
OpStore %6 %22
|
||||
%4 = OpConstantNull %3
|
||||
%1 = OpVariable %2 Output %4
|
||||
%6 = OpTypePointer Input %3
|
||||
%5 = OpVariable %6 Input
|
||||
%7 = OpVariable %2 Output %4
|
||||
%9 = OpTypeVoid
|
||||
%10 = OpTypeStruct %3
|
||||
%8 = OpTypeFunction %9 %10
|
||||
%15 = OpTypeFunction %9
|
||||
%19 = OpConstant %3 42
|
||||
%20 = OpConstantComposite %10 %19
|
||||
%21 = OpTypeFunction %9 %3
|
||||
%11 = OpFunction %9 None %8
|
||||
%12 = OpFunctionParameter %10
|
||||
%13 = OpLabel
|
||||
%14 = OpCompositeExtract %3 %12 0
|
||||
OpStore %1 %14
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%24 = OpFunction %14 None %23
|
||||
%25 = OpLabel
|
||||
%26 = OpFunctionCall %14 %17 %29
|
||||
%16 = OpFunction %9 None %15
|
||||
%17 = OpLabel
|
||||
%18 = OpFunctionCall %9 %11 %20
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%31 = OpFunction %14 None %30
|
||||
%32 = OpFunctionParameter %4
|
||||
%33 = OpLabel
|
||||
OpStore %12 %32
|
||||
%22 = OpFunction %9 None %21
|
||||
%23 = OpFunctionParameter %3
|
||||
%24 = OpLabel
|
||||
OpStore %7 %23
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%34 = OpFunction %14 None %23
|
||||
%35 = OpLabel
|
||||
%36 = OpLoad %4 %11
|
||||
%37 = OpCompositeConstruct %16 %36
|
||||
%39 = OpLoad %4 %9
|
||||
%40 = OpCompositeConstruct %38 %39 %37
|
||||
%42 = OpCompositeExtract %4 %40 0
|
||||
%43 = OpCompositeExtract %16 %40 1
|
||||
%44 = OpCompositeExtract %4 %43 0
|
||||
%45 = OpFMul %4 %42 %44
|
||||
%41 = OpFunctionCall %14 %31 %45
|
||||
%25 = OpFunction %9 None %15
|
||||
%26 = OpLabel
|
||||
%27 = OpLoad %3 %5
|
||||
%28 = OpCompositeConstruct %10 %27
|
||||
%30 = OpCompositeExtract %3 %28 0
|
||||
%29 = OpFunctionCall %9 %22 %30
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)");
|
||||
|
|
|
@ -172,6 +172,7 @@ source_set("tint_unittests_core_src") {
|
|||
"../src/resolver/builtins_validation_test.cc",
|
||||
"../src/resolver/control_block_validation_test.cc",
|
||||
"../src/resolver/decoration_validation_test.cc",
|
||||
"../src/resolver/entry_point_validation_test.cc",
|
||||
"../src/resolver/function_validation_test.cc",
|
||||
"../src/resolver/host_shareable_validation_test.cc",
|
||||
"../src/resolver/intrinsic_test.cc",
|
||||
|
|
Loading…
Reference in New Issue