validation: clean up function attributes validation and unit-tests

- clean up function decorations unit tests
- clean up interpolate and invariant validation and unittest
- add separate unit-tests for each shader stage input and output
- add [[builtin(position)]] tests
- add validation and test for:
structures with 'location' decorated members cannot be used as compute shaders input

Bug: tint:1007
Change-Id: I12e97e163b3a77bc76ce21faba241683eec5d917
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/58942
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: James Price <jrprice@google.com>
This commit is contained in:
Sarah 2021-07-23 13:23:20 +00:00 committed by Sarah Mashayekhi
parent f76c227cee
commit 465c5aa51d
5 changed files with 531 additions and 321 deletions

View File

@ -48,6 +48,16 @@ constexpr Params ParamsFor(ast::Builtin builtin,
return Params{DataType<T>::AST, builtin, stage, is_valid}; return Params{DataType<T>::AST, builtin, stage, is_valid};
} }
static constexpr Params cases[] = { static constexpr Params cases[] = {
ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
ast::PipelineStage::kVertex,
false),
ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
ast::PipelineStage::kFragment,
true),
ParamsFor<vec4<f32>>(ast::Builtin::kPosition,
ast::PipelineStage::kCompute,
false),
ParamsFor<u32>(ast::Builtin::kVertexIndex, ParamsFor<u32>(ast::Builtin::kVertexIndex,
ast::PipelineStage::kVertex, ast::PipelineStage::kVertex,
true), true),
@ -183,7 +193,7 @@ INSTANTIATE_TEST_SUITE_P(ResolverBuiltinsValidationTest,
TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInput_Fail) { TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInput_Fail) {
// [[stage(fragment)]] // [[stage(fragment)]]
// fn fs_main( // fn fs_main(
// [[builtin(kFragDepth)]] fd: f32, // [[builtin(frag_depth)]] fd: f32,
// ) -> [[location(0)]] f32 { return 1.0; } // ) -> [[location(0)]] f32 { return 1.0; }
auto* fd = Param( auto* fd = Param(
"fd", ty.f32(), "fd", ty.f32(),
@ -197,9 +207,9 @@ TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInput_Fail) {
"fragment pipeline stage"); "fragment pipeline stage");
} }
TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Fail) { TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Ignored) {
// Struct MyInputs { // struct MyInputs {
// [[builtin(front_facing)]] ff: bool; // [[builtin(frag_depth)]] ff: f32;
// }; // };
// [[stage(fragment)]] // [[stage(fragment)]]
// fn fragShader(arg: MyInputs) -> [[location(0)]] f32 { return 1.0; } // fn fragShader(arg: MyInputs) -> [[location(0)]] f32 { return 1.0; }
@ -211,11 +221,7 @@ TEST_F(ResolverBuiltinsValidationTest, FragDepthIsInputStruct_Fail) {
Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)}, Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
{Stage(ast::PipelineStage::kFragment)}, {Location(0)}); {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
EXPECT_FALSE(r()->Resolve()); EXPECT_TRUE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: builtin(frag_depth) cannot be used in input of fragment "
"pipeline stage\nnote: while analysing entry point fragShader");
} }
} // namespace StageTest } // namespace StageTest

View File

@ -13,11 +13,6 @@
// limitations under the License. // limitations under the License.
#include "src/ast/disable_validation_decoration.h" #include "src/ast/disable_validation_decoration.h"
#include "src/ast/override_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.h"
#include "src/resolver/resolver_test_helper.h" #include "src/resolver/resolver_test_helper.h"
@ -55,7 +50,6 @@ using u32 = builder::u32;
namespace DecorationTests { namespace DecorationTests {
namespace { namespace {
enum class DecorationKind { enum class DecorationKind {
kAlign, kAlign,
kBinding, kBinding,
@ -75,7 +69,7 @@ enum class DecorationKind {
kBindingAndGroup, kBindingAndGroup,
}; };
bool IsBindingDecoration(DecorationKind kind) { static bool IsBindingDecoration(DecorationKind kind) {
switch (kind) { switch (kind) {
case DecorationKind::kBinding: case DecorationKind::kBinding:
case DecorationKind::kGroup: case DecorationKind::kGroup:
@ -106,8 +100,7 @@ static ast::DecorationList createDecorations(const Source& source,
return {builder.create<ast::GroupDecoration>(source, 1u)}; return {builder.create<ast::GroupDecoration>(source, 1u)};
case DecorationKind::kInterpolate: case DecorationKind::kInterpolate:
return {builder.Interpolate(source, ast::InterpolationType::kLinear, return {builder.Interpolate(source, ast::InterpolationType::kLinear,
ast::InterpolationSampling::kCenter), ast::InterpolationSampling::kCenter)};
builder.Location(0)};
case DecorationKind::kInvariant: case DecorationKind::kInvariant:
return {builder.Invariant(source)}; return {builder.Invariant(source)};
case DecorationKind::kLocation: case DecorationKind::kLocation:
@ -117,7 +110,7 @@ static ast::DecorationList createDecorations(const Source& source,
case DecorationKind::kOffset: case DecorationKind::kOffset:
return {builder.create<ast::StructMemberOffsetDecoration>(source, 4u)}; return {builder.create<ast::StructMemberOffsetDecoration>(source, 4u)};
case DecorationKind::kSize: case DecorationKind::kSize:
return {builder.create<ast::StructMemberSizeDecoration>(source, 4u)}; return {builder.create<ast::StructMemberSizeDecoration>(source, 16u)};
case DecorationKind::kStage: case DecorationKind::kStage:
return {builder.Stage(source, ast::PipelineStage::kCompute)}; return {builder.Stage(source, ast::PipelineStage::kCompute)};
case DecorationKind::kStride: case DecorationKind::kStride:
@ -134,6 +127,7 @@ static ast::DecorationList createDecorations(const Source& source,
return {}; return {};
} }
namespace FunctionInputAndOutputTests {
using FunctionParameterDecorationTest = TestWithParams; using FunctionParameterDecorationTest = TestWithParams;
TEST_P(FunctionParameterDecorationTest, IsValid) { TEST_P(FunctionParameterDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
@ -171,114 +165,6 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kWorkgroup, false}, TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false})); TestParams{DecorationKind::kBindingAndGroup, false}));
using EntryPointParameterDecorationTest = TestWithParams;
TEST_P(EntryPointParameterDecorationTest, IsValid) {
auto& params = GetParam();
Func("main",
ast::VariableList{Param("a", ty.vec4<f32>(),
createDecorations({}, *this, params.kind))},
ty.void_(), {},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)});
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"error: decoration is not valid for function parameters");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
EntryPointParameterDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, true},
// kInvariant tested separately (requires position builtin)
TestParams{DecorationKind::kLocation, true},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
TEST_F(EntryPointParameterDecorationTest, DuplicateDecoration) {
Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
{Stage(ast::PipelineStage::kFragment)},
{
Location(Source{{12, 34}}, 2),
Location(Source{{56, 78}}, 3),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate location decoration
12:34 note: first decoration declared here)");
}
TEST_F(EntryPointParameterDecorationTest, DuplicateInternalDecoration) {
auto* s =
Param("s", ty.sampler(ast::SamplerKind::kSampler),
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
ASTNodes().Create<ast::DisableValidationDecoration>(
ID(), ast::DisabledValidation::kBindingPointCollision),
ASTNodes().Create<ast::DisableValidationDecoration>(
ID(), ast::DisabledValidation::kEntryPointParameter),
});
Func("f", {s}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(EntryPointParameterDecorationTest, ComputeShaderLocation) {
auto* input = Param("input", ty.vec4<f32>(),
ast::DecorationList{Location(Source{{12, 34}}, 1)});
Func("main", {input}, ty.void_(), {},
{Stage(ast::PipelineStage::kCompute),
create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for compute shader function "
"parameters");
}
TEST_F(EntryPointParameterDecorationTest, InvariantWithPosition) {
auto* param = Param("p", ty.vec4<f32>(),
{Invariant(Source{{12, 34}}),
Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
Func("main", ast::VariableList{param}, ty.vec4<f32>(),
ast::StatementList{Return(Construct(ty.vec4<f32>()))},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
ast::DecorationList{
Location(0),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(EntryPointParameterDecorationTest, InvariantWithoutPosition) {
auto* param =
Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
Func("main", ast::VariableList{param}, ty.vec4<f32>(),
ast::StatementList{Return(Construct(ty.vec4<f32>()))},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
ast::DecorationList{
Location(0),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: invariant attribute must only be applied to a "
"position builtin");
}
using FunctionReturnTypeDecorationTest = TestWithParams; using FunctionReturnTypeDecorationTest = TestWithParams;
TEST_P(FunctionReturnTypeDecorationTest, IsValid) { TEST_P(FunctionReturnTypeDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
@ -313,41 +199,46 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}, TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false})); TestParams{DecorationKind::kBindingAndGroup, false}));
} // namespace FunctionInputAndOutputTests
using EntryPointReturnTypeDecorationTest = TestWithParams; namespace EntryPointInputAndOutputTests {
TEST_P(EntryPointReturnTypeDecorationTest, IsValid) { using ComputeShaderParameterDecorationTest = TestWithParams;
TEST_P(ComputeShaderParameterDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
auto* p = Param("a", ty.vec4<f32>(),
Func("main", ast::VariableList{}, ty.vec4<f32>(), createDecorations(Source{{12, 34}}, *this, params.kind));
{Return(Construct(ty.vec4<f32>(), 1.f))}, Func("main", ast::VariableList{p}, ty.void_(), {},
{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)}, {Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
createDecorations({}, *this, params.kind));
if (params.should_pass) { if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
} else { } else {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
if (params.kind == DecorationKind::kLocation || if (params.kind == DecorationKind::kBuiltin) {
params.kind == DecorationKind::kInterpolate) {
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"error: decoration is not valid for compute shader entry point " "12:34 error: builtin(position) cannot be used in input of "
"return types"); "compute pipeline stage");
} else if (params.kind == DecorationKind::kInterpolate ||
params.kind == DecorationKind::kLocation ||
params.kind == DecorationKind::kInvariant) {
EXPECT_EQ(
r()->error(),
"12:34 error: decoration is not valid for compute shader inputs");
} else { } else {
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"error: decoration is not valid for entry point return types"); "12:34 error: decoration is not valid for function parameters");
} }
} }
} }
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest, ResolverDecorationValidationTest,
EntryPointReturnTypeDecorationTest, ComputeShaderParameterDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false}, testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false}, TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true}, TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false}, TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, false}, TestParams{DecorationKind::kInterpolate, false},
// kInvariant tested separately (requires position builtin) TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, false}, TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, false}, TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false}, TestParams{DecorationKind::kOffset, false},
@ -358,6 +249,271 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kWorkgroup, false}, TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false})); TestParams{DecorationKind::kBindingAndGroup, false}));
using FragmentShaderParameterDecorationTest = TestWithParams;
TEST_P(FragmentShaderParameterDecorationTest, IsValid) {
auto& params = GetParam();
auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
if (params.kind != DecorationKind::kBuiltin &&
params.kind != DecorationKind::kLocation) {
decos.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
}
auto* p = Param("a", ty.vec4<f32>(), decos);
Func("frag_main", {p}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for function parameters");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
FragmentShaderParameterDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, true},
TestParams{DecorationKind::kInvariant, true},
TestParams{DecorationKind::kLocation, true},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using VertexShaderParameterDecorationTest = TestWithParams;
TEST_P(VertexShaderParameterDecorationTest, IsValid) {
auto& params = GetParam();
auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
if (params.kind != DecorationKind::kLocation) {
decos.push_back(Location(Source{{34, 56}}, 2));
}
auto* p = Param("a", ty.vec4<f32>(), decos);
Func("vertex_main", ast::VariableList{p}, ty.vec4<f32>(),
{Return(Construct(ty.vec4<f32>()))},
{Stage(ast::PipelineStage::kVertex)},
{Builtin(ast::Builtin::kPosition)});
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
if (params.kind == DecorationKind::kBuiltin) {
EXPECT_EQ(r()->error(),
"12:34 error: builtin(position) cannot be used in input of "
"vertex pipeline stage");
} else if (params.kind == DecorationKind::kInvariant) {
EXPECT_EQ(r()->error(),
"12:34 error: invariant attribute must only be applied to a "
"position builtin");
} else {
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for function parameters");
}
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
VertexShaderParameterDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, true},
TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, true},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using ComputeShaderReturnTypeDecorationTest = TestWithParams;
TEST_P(ComputeShaderReturnTypeDecorationTest, IsValid) {
auto& params = GetParam();
Func("main", ast::VariableList{}, ty.vec4<f32>(),
{Return(Construct(ty.vec4<f32>(), 1.f))},
{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)},
createDecorations(Source{{12, 34}}, *this, params.kind));
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
if (params.kind == DecorationKind::kBuiltin) {
EXPECT_EQ(r()->error(),
"12:34 error: builtin(position) cannot be used in output of "
"compute pipeline stage");
} else if (params.kind == DecorationKind::kInterpolate ||
params.kind == DecorationKind::kLocation ||
params.kind == DecorationKind::kInvariant) {
EXPECT_EQ(
r()->error(),
"12:34 error: decoration is not valid for compute shader output");
} else {
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for entry point return "
"types");
}
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
ComputeShaderReturnTypeDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, false},
TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using FragmentShaderReturnTypeDecorationTest = TestWithParams;
TEST_P(FragmentShaderReturnTypeDecorationTest, IsValid) {
auto& params = GetParam();
auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
decos.push_back(Location(Source{{34, 56}}, 2));
Func("frag_main", {}, ty.vec4<f32>(), {Return(Construct(ty.vec4<f32>()))},
{Stage(ast::PipelineStage::kFragment)}, decos);
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
if (params.kind == DecorationKind::kBuiltin) {
EXPECT_EQ(r()->error(),
"12:34 error: builtin(position) cannot be used in output of "
"fragment pipeline stage");
} else if (params.kind == DecorationKind::kInvariant) {
EXPECT_EQ(r()->error(),
"12:34 error: invariant attribute must only be applied to a "
"position builtin");
} else if (params.kind == DecorationKind::kLocation) {
EXPECT_EQ(r()->error(),
"34:56 error: duplicate location decoration\n"
"12:34 note: first decoration declared here");
} else {
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for entry point return "
"types");
}
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
FragmentShaderReturnTypeDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, true},
TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using VertexShaderReturnTypeDecorationTest = TestWithParams;
TEST_P(VertexShaderReturnTypeDecorationTest, IsValid) {
auto& params = GetParam();
auto decos = createDecorations(Source{{12, 34}}, *this, params.kind);
// a vertex shader must include the 'position' builtin in its return type
if (params.kind != DecorationKind::kBuiltin) {
decos.push_back(Builtin(Source{{34, 56}}, ast::Builtin::kPosition));
}
Func("vertex_main", ast::VariableList{}, ty.vec4<f32>(),
{Return(Construct(ty.vec4<f32>()))},
{Stage(ast::PipelineStage::kVertex)}, decos);
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
if (params.kind == DecorationKind::kLocation) {
EXPECT_EQ(r()->error(),
"34:56 error: multiple entry point IO attributes\n"
"12:34 note: previously consumed location(1)");
} else {
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for entry point return "
"types");
}
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
VertexShaderReturnTypeDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, true},
TestParams{DecorationKind::kInvariant, true},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false}));
using EntryPointParameterDecorationTest = TestWithParams;
TEST_F(EntryPointParameterDecorationTest, DuplicateDecoration) {
Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
{Stage(ast::PipelineStage::kFragment)},
{
Location(Source{{12, 34}}, 2),
Location(Source{{56, 78}}, 3),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate location decoration
12:34 note: first decoration declared here)");
}
TEST_F(EntryPointParameterDecorationTest, DuplicateInternalDecoration) {
auto* s =
Param("s", ty.sampler(ast::SamplerKind::kSampler),
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
ASTNodes().Create<ast::DisableValidationDecoration>(
ID(), ast::DisabledValidation::kBindingPointCollision),
ASTNodes().Create<ast::DisableValidationDecoration>(
ID(), ast::DisabledValidation::kEntryPointParameter),
});
Func("f", {s}, ty.void_(), {}, {Stage(ast::PipelineStage::kFragment)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
using EntryPointReturnTypeDecorationTest = ResolverTest;
TEST_F(EntryPointReturnTypeDecorationTest, DuplicateDecoration) { TEST_F(EntryPointReturnTypeDecorationTest, DuplicateDecoration) {
Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)}, Func("main", ast::VariableList{}, ty.f32(), ast::StatementList{Return(1.f)},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)}, ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
@ -372,73 +528,20 @@ TEST_F(EntryPointReturnTypeDecorationTest, DuplicateDecoration) {
12:34 note: first decoration declared here)"); 12:34 note: first decoration declared here)");
} }
TEST_F(EntryPointReturnTypeDecorationTest, InvariantWithPosition) { TEST_F(EntryPointReturnTypeDecorationTest, DuplicateInternalDecoration) {
Func("main", ast::VariableList{}, ty.vec4<f32>(), Func("f", {}, ty.i32(), {Return(1)}, {Stage(ast::PipelineStage::kFragment)},
ast::StatementList{Return(Construct(ty.vec4<f32>()))},
ast::DecorationList{Stage(ast::PipelineStage::kVertex)},
ast::DecorationList{ ast::DecorationList{
Invariant(Source{{12, 34}}), ASTNodes().Create<ast::DisableValidationDecoration>(
Builtin(Source{{56, 78}}, ast::Builtin::kPosition), ID(), ast::DisabledValidation::kBindingPointCollision),
ASTNodes().Create<ast::DisableValidationDecoration>(
ID(), ast::DisabledValidation::kEntryPointParameter),
}); });
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
} }
} // namespace EntryPointInputAndOutputTests
TEST_F(EntryPointReturnTypeDecorationTest, InvariantWithoutPosition) { namespace StructAndStructMemberTests {
Func("main", ast::VariableList{}, ty.vec4<f32>(),
ast::StatementList{Return(Construct(ty.vec4<f32>()))},
ast::DecorationList{Stage(ast::PipelineStage::kVertex)},
ast::DecorationList{
Invariant(Source{{12, 34}}),
Location(Source{{56, 78}}, 0),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: invariant attribute must only be applied to a "
"position builtin");
}
using ArrayDecorationTest = TestWithParams;
TEST_P(ArrayDecorationTest, IsValid) {
auto& params = GetParam();
auto* arr = ty.array(ty.f32(), 0,
createDecorations(Source{{12, 34}}, *this, params.kind));
Structure("mystruct",
{
Member("a", arr),
},
{create<ast::StructBlockDecoration>()});
WrapInFunction();
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for array types");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
ArrayDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, false},
TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, 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},
TestParams{DecorationKind::kBindingAndGroup, false}));
using StructDecorationTest = TestWithParams; using StructDecorationTest = TestWithParams;
TEST_P(StructDecorationTest, IsValid) { TEST_P(StructDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
@ -484,19 +587,15 @@ TEST_F(StructDecorationTest, DuplicateDecoration) {
create<ast::StructBlockDecoration>(Source{{12, 34}}), create<ast::StructBlockDecoration>(Source{{12, 34}}),
create<ast::StructBlockDecoration>(Source{{56, 78}}), create<ast::StructBlockDecoration>(Source{{56, 78}}),
}); });
WrapInFunction(); WrapInFunction();
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate block decoration R"(56:78 error: duplicate block decoration
12:34 note: first decoration declared here)"); 12:34 note: first decoration declared here)");
} }
using StructMemberDecorationTest = TestWithParams; using StructMemberDecorationTest = TestWithParams;
TEST_P(StructMemberDecorationTest, IsValid) { TEST_P(StructMemberDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
ast::StructMemberList members; ast::StructMemberList members;
if (params.kind == DecorationKind::kBuiltin) { if (params.kind == DecorationKind::kBuiltin) {
members.push_back( members.push_back(
@ -507,11 +606,8 @@ TEST_P(StructMemberDecorationTest, IsValid) {
{Member("a", ty.f32(), {Member("a", ty.f32(),
createDecorations(Source{{12, 34}}, *this, params.kind))}); createDecorations(Source{{12, 34}}, *this, params.kind))});
} }
Structure("mystruct", members); Structure("mystruct", members);
WrapInFunction(); WrapInFunction();
if (params.should_pass) { if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
} else { } else {
@ -538,7 +634,6 @@ INSTANTIATE_TEST_SUITE_P(
TestParams{DecorationKind::kStructBlock, false}, TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}, TestParams{DecorationKind::kWorkgroup, false},
TestParams{DecorationKind::kBindingAndGroup, false})); TestParams{DecorationKind::kBindingAndGroup, false}));
TEST_F(StructMemberDecorationTest, DuplicateDecoration) { TEST_F(StructMemberDecorationTest, DuplicateDecoration) {
Structure("mystruct", { Structure("mystruct", {
Member("a", ty.i32(), Member("a", ty.i32(),
@ -549,15 +644,12 @@ TEST_F(StructMemberDecorationTest, DuplicateDecoration) {
Source{{56, 78}}, 8u), Source{{56, 78}}, 8u),
}), }),
}); });
WrapInFunction(); WrapInFunction();
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
R"(56:78 error: duplicate align decoration R"(56:78 error: duplicate align decoration
12:34 note: first decoration declared here)"); 12:34 note: first decoration declared here)");
} }
TEST_F(StructMemberDecorationTest, InvariantDecorationWithPosition) { TEST_F(StructMemberDecorationTest, InvariantDecorationWithPosition) {
Structure("mystruct", { Structure("mystruct", {
Member("a", ty.vec4<f32>(), Member("a", ty.vec4<f32>(),
@ -566,11 +658,9 @@ TEST_F(StructMemberDecorationTest, InvariantDecorationWithPosition) {
Builtin(ast::Builtin::kPosition), Builtin(ast::Builtin::kPosition),
}), }),
}); });
WrapInFunction(); WrapInFunction();
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
} }
TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) { TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) {
Structure("mystruct", { Structure("mystruct", {
Member("a", ty.vec4<f32>(), Member("a", ty.vec4<f32>(),
@ -578,7 +668,6 @@ TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) {
Invariant(Source{{12, 34}}), Invariant(Source{{12, 34}}),
}), }),
}); });
WrapInFunction(); WrapInFunction();
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
@ -586,6 +675,49 @@ TEST_F(StructMemberDecorationTest, InvariantDecorationWithoutPosition) {
"position builtin"); "position builtin");
} }
} // namespace StructAndStructMemberTests
using ArrayDecorationTest = TestWithParams;
TEST_P(ArrayDecorationTest, IsValid) {
auto& params = GetParam();
auto* arr = ty.array(ty.f32(), 0,
createDecorations(Source{{12, 34}}, *this, params.kind));
Structure("mystruct",
{
Member("a", arr),
},
{create<ast::StructBlockDecoration>()});
WrapInFunction();
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for array types");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
ArrayDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, false},
TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, 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},
TestParams{DecorationKind::kBindingAndGroup, false}));
using VariableDecorationTest = TestWithParams; using VariableDecorationTest = TestWithParams;
TEST_P(VariableDecorationTest, IsValid) { TEST_P(VariableDecorationTest, IsValid) {
auto& params = GetParam(); auto& params = GetParam();
@ -697,41 +829,6 @@ TEST_F(ConstantDecorationTest, DuplicateDecoration) {
12:34 note: first decoration declared here)"); 12:34 note: first decoration declared here)");
} }
using FunctionDecorationTest = TestWithParams;
TEST_P(FunctionDecorationTest, IsValid) {
auto& params = GetParam();
ast::DecorationList decos =
createDecorations(Source{{12, 34}}, *this, params.kind);
Func("foo", ast::VariableList{}, ty.void_(), ast::StatementList{}, decos);
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for functions");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
FunctionDecorationTest,
testing::Values(TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kInterpolate, false},
TestParams{DecorationKind::kInvariant, false},
TestParams{DecorationKind::kLocation, false},
TestParams{DecorationKind::kOverride, false},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
// Skip kStage as we do not apply it in this test
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
// Skip kWorkgroup as this is a different error
TestParams{DecorationKind::kBindingAndGroup, false}));
} // namespace } // namespace
} // namespace DecorationTests } // namespace DecorationTests
@ -1046,6 +1143,108 @@ TEST_F(ResourceDecorationTest, BindingPointOnNonResource) {
} // namespace } // namespace
} // namespace ResourceTests } // namespace ResourceTests
namespace LocationDecorationTests {
namespace {
using LocationDecorationTests = ResolverTest;
TEST_F(LocationDecorationTests, ComputeShaderLocation_Input) {
Func("main", {}, ty.i32(), {Return(Expr(1))},
{Stage(ast::PipelineStage::kCompute),
create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))},
ast::DecorationList{Location(Source{{12, 34}}, 1)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for compute shader output");
}
TEST_F(LocationDecorationTests, ComputeShaderLocation_Output) {
auto* input = Param("input", ty.i32(),
ast::DecorationList{Location(Source{{12, 34}}, 0u)});
Func("main", {input}, ty.void_(), {},
{Stage(ast::PipelineStage::kCompute),
create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for compute shader inputs");
}
TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Output) {
auto* m = Member("m", ty.i32(),
ast::DecorationList{Location(Source{{12, 34}}, 0u)});
auto* s = Structure("S", {m});
Func(Source{{56, 78}}, "main", {}, ty.Of(s),
ast::StatementList{Return(Expr(Construct(ty.Of(s))))},
{Stage(ast::PipelineStage::kCompute),
create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for compute shader output\n"
"56:78 note: while analysing entry point 'main'");
}
TEST_F(LocationDecorationTests, ComputeShaderLocationStructMember_Input) {
auto* m = Member("m", ty.i32(),
ast::DecorationList{Location(Source{{12, 34}}, 0u)});
auto* s = Structure("S", {m});
auto* input = Param("input", ty.Of(s));
Func(Source{{56, 78}}, "main", {input}, ty.void_(), {},
{Stage(ast::PipelineStage::kCompute),
create<ast::WorkgroupDecoration>(Source{{12, 34}}, Expr(1))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: decoration is not valid for compute shader inputs\n"
"56:78 note: while analysing entry point 'main'");
}
TEST_F(LocationDecorationTests, BadType) {
auto* p = Param(Source{{12, 34}}, "a", ty.mat2x2<f32>(), {Location(0)});
Func("frag_main", {p}, ty.void_(), {},
{Stage(ast::PipelineStage::kFragment)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: User defined entry point IO types must be a numeric "
"scalar, a numeric vector, or a structure");
}
} // namespace
} // namespace LocationDecorationTests
namespace InvariantDecorationTests {
namespace {
using InvariantDecorationTests = ResolverTest;
TEST_F(InvariantDecorationTests, InvariantWithPosition) {
auto* param = Param("p", ty.vec4<f32>(),
{Invariant(Source{{12, 34}}),
Builtin(Source{{56, 78}}, ast::Builtin::kPosition)});
Func("main", ast::VariableList{param}, ty.vec4<f32>(),
ast::StatementList{Return(Construct(ty.vec4<f32>()))},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
ast::DecorationList{
Location(0),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(InvariantDecorationTests, InvariantWithoutPosition) {
auto* param =
Param("p", ty.vec4<f32>(), {Invariant(Source{{12, 34}}), Location(0)});
Func("main", ast::VariableList{param}, ty.vec4<f32>(),
ast::StatementList{Return(Construct(ty.vec4<f32>()))},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
ast::DecorationList{
Location(0),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: invariant attribute must only be applied to a "
"position builtin");
}
} // namespace
} // namespace InvariantDecorationTests
namespace WorkgroupDecorationTests { namespace WorkgroupDecorationTests {
namespace { namespace {

View File

@ -160,7 +160,7 @@ TEST_F(ResolverEntryPointValidationTest,
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0) 13:43 note: previously consumed location(0)
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, TEST_F(ResolverEntryPointValidationTest,
@ -183,7 +183,7 @@ TEST_F(ResolverEntryPointValidationTest,
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
R"(14:52 error: missing entry point IO attribute R"(14:52 error: missing entry point IO attribute
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_NestedStruct) { TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_NestedStruct) {
@ -209,7 +209,7 @@ TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_NestedStruct) {
EXPECT_EQ( EXPECT_EQ(
r()->error(), r()->error(),
R"(14:52 error: entry point IO types cannot contain nested structures R"(14:52 error: entry point IO types cannot contain nested structures
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_RuntimeArray) { TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_RuntimeArray) {
@ -254,7 +254,7 @@ TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
EXPECT_EQ( EXPECT_EQ(
r()->error(), r()->error(),
R"(12:34 error: builtin(frag_depth) attribute appears multiple times as pipeline output R"(12:34 error: builtin(frag_depth) attribute appears multiple times as pipeline output
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateLocation) { TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateLocation) {
@ -276,7 +276,7 @@ TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateLocation) {
EXPECT_EQ( EXPECT_EQ(
r()->error(), r()->error(),
R"(12:34 error: location(1) attribute appears multiple times as pipeline output R"(12:34 error: location(1) attribute appears multiple times as pipeline output
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) { TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
@ -369,7 +369,7 @@ TEST_F(ResolverEntryPointValidationTest,
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
13:43 note: previously consumed location(0) 13:43 note: previously consumed location(0)
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, TEST_F(ResolverEntryPointValidationTest,
@ -389,7 +389,7 @@ TEST_F(ResolverEntryPointValidationTest,
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(14:52 error: missing entry point IO attribute EXPECT_EQ(r()->error(), R"(14:52 error: missing entry point IO attribute
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_NestedStruct) { TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_NestedStruct) {
@ -413,7 +413,7 @@ TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_NestedStruct) {
EXPECT_EQ( EXPECT_EQ(
r()->error(), r()->error(),
R"(14:52 error: entry point IO types cannot contain nested structures R"(14:52 error: entry point IO types cannot contain nested structures
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_RuntimeArray) { TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_RuntimeArray) {
@ -477,7 +477,7 @@ TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateBuiltins) {
EXPECT_EQ( EXPECT_EQ(
r()->error(), r()->error(),
R"(12:34 error: builtin(sample_index) attribute appears multiple times as pipeline input R"(12:34 error: builtin(sample_index) attribute appears multiple times as pipeline input
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateLocation) { TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateLocation) {
@ -515,7 +515,7 @@ TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateLocation) {
EXPECT_EQ( EXPECT_EQ(
r()->error(), r()->error(),
R"(12:34 error: location(1) attribute appears multiple times as pipeline input R"(12:34 error: location(1) attribute appears multiple times as pipeline input
12:34 note: while analysing entry point main)"); 12:34 note: while analysing entry point 'main')");
} }
TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) { TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {

View File

@ -1138,18 +1138,9 @@ bool Resolver::ValidateFunctionParameter(const ast::Function* func,
"decoration is not valid for non-entry point function parameters", "decoration is not valid for non-entry point function parameters",
deco->source()); deco->source());
return false; return false;
} else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
if (!ValidateInterpolateDecoration(interpolate, info->type)) {
return false;
}
} else if (deco->Is<ast::LocationDecoration>()) {
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
AddError(
"decoration is not valid for compute shader function parameters",
deco->source());
return false;
}
} else if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InvariantDecoration, } else if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InvariantDecoration,
ast::LocationDecoration,
ast::InterpolateDecoration,
ast::InternalDecoration>() && ast::InternalDecoration>() &&
(IsValidationEnabled( (IsValidationEnabled(
info->declaration->decorations(), info->declaration->decorations(),
@ -1198,7 +1189,8 @@ bool Resolver::ValidateFunctionParameter(const ast::Function* func,
bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco, bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
const sem::Type* storage_type, const sem::Type* storage_type,
const bool is_input) { const bool is_input,
const bool is_struct_member) {
auto* type = storage_type->UnwrapRef(); auto* type = storage_type->UnwrapRef();
const auto stage = current_function_ const auto stage = current_function_
? current_function_->declaration->pipeline_stage() ? current_function_->declaration->pipeline_stage()
@ -1206,15 +1198,13 @@ bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
std::stringstream stage_name; std::stringstream stage_name;
stage_name << stage; stage_name << stage;
bool is_stage_mismatch = false; bool is_stage_mismatch = false;
bool is_output = !is_input;
switch (deco->value()) { switch (deco->value()) {
case ast::Builtin::kPosition: case ast::Builtin::kPosition:
if (stage != ast::PipelineStage::kNone && if (stage != ast::PipelineStage::kNone &&
!(stage == ast::PipelineStage::kFragment && is_input) && !((is_input && stage == ast::PipelineStage::kFragment) ||
!(stage == ast::PipelineStage::kVertex && !is_input)) { (is_output && stage == ast::PipelineStage::kVertex))) {
AddError(deco_to_str(deco) + " cannot be used in " + is_stage_mismatch = true;
(is_input ? "input of " : "output of ") +
stage_name.str() + " pipeline stage",
deco->source());
} }
if (!(type->is_float_vector() && type->As<sem::Vector>()->Width() == 4)) { if (!(type->is_float_vector() && type->As<sem::Vector>()->Width() == 4)) {
AddError("store type of " + deco_to_str(deco) + " must be 'vec4<f32>'", AddError("store type of " + deco_to_str(deco) + " must be 'vec4<f32>'",
@ -1311,12 +1301,16 @@ bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
break; break;
} }
if (is_stage_mismatch) { // ignore builtin attribute on struct members to facillate data movement
AddError(deco_to_str(deco) + " cannot be used in " + // between stages
(is_input ? "input of " : "output of ") + stage_name.str() + if (!is_struct_member) {
" pipeline stage", if (is_stage_mismatch) {
deco->source()); AddError(deco_to_str(deco) + " cannot be used in " +
return false; (is_input ? "input of " : "output of ") + stage_name.str() +
" pipeline stage",
deco->source());
return false;
}
} }
return true; return true;
@ -1409,28 +1403,14 @@ bool Resolver::ValidateFunction(const ast::Function* func,
deco->source()); deco->source());
return false; return false;
} }
if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InternalDecoration,
if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) { ast::LocationDecoration, ast::InterpolateDecoration,
if (!ValidateInterpolateDecoration(interpolate, info->return_type)) { ast::InvariantDecoration>() &&
return false; (IsValidationEnabled(info->declaration->decorations(),
} ast::DisabledValidation::kEntryPointParameter) &&
} else if (deco->Is<ast::LocationDecoration>()) { IsValidationEnabled(info->declaration->decorations(),
if (func->pipeline_stage() == ast::PipelineStage::kCompute) { ast::DisabledValidation::
AddError( kIgnoreConstructibleFunctionParameter))) {
"decoration is not valid for compute shader entry point return "
"types",
deco->source());
return false;
}
} else if (!deco->IsAnyOf<ast::BuiltinDecoration, ast::InternalDecoration,
ast::InvariantDecoration>() &&
(IsValidationEnabled(
info->declaration->decorations(),
ast::DisabledValidation::kEntryPointParameter) &&
IsValidationEnabled(
info->declaration->decorations(),
ast::DisabledValidation::
kIgnoreConstructibleFunctionParameter))) {
AddError("decoration is not valid for entry point return types", AddError("decoration is not valid for entry point return types",
deco->source()); deco->source());
return false; return false;
@ -1473,6 +1453,7 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
ast::Decoration* pipeline_io_attribute = nullptr; ast::Decoration* pipeline_io_attribute = nullptr;
ast::InvariantDecoration* invariant_attribute = nullptr; ast::InvariantDecoration* invariant_attribute = nullptr;
for (auto* deco : decos) { for (auto* deco : decos) {
auto is_invalid_compute_shader_decoration = false;
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) { if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
if (pipeline_io_attribute) { if (pipeline_io_attribute) {
AddError("multiple entry point IO attributes", deco->source()); AddError("multiple entry point IO attributes", deco->source());
@ -1490,14 +1471,14 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
func->source()); func->source());
return false; return false;
} }
builtins.emplace(builtin->value());
if (!ValidateBuiltinDecoration( if (!ValidateBuiltinDecoration(
builtin, ty, builtin, ty,
/* is_input */ param_or_ret == ParamOrRetType::kParameter)) { /* is_input */ param_or_ret == ParamOrRetType::kParameter,
/* is_struct_member */ is_struct_member)) {
return false; return false;
} }
builtins.emplace(builtin->value());
} else if (auto* location = deco->As<ast::LocationDecoration>()) { } else if (auto* location = deco->As<ast::LocationDecoration>()) {
if (pipeline_io_attribute) { if (pipeline_io_attribute) {
AddError("multiple entry point IO attributes", deco->source()); AddError("multiple entry point IO attributes", deco->source());
@ -1515,10 +1496,31 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
func->source()); func->source());
return false; return false;
} }
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
is_invalid_compute_shader_decoration = true;
}
locations.emplace(location->value()); locations.emplace(location->value());
} else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
is_invalid_compute_shader_decoration = true;
} else if (!ValidateInterpolateDecoration(interpolate, ty)) {
return false;
}
} else if (auto* invariant = deco->As<ast::InvariantDecoration>()) { } else if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
is_invalid_compute_shader_decoration = true;
}
invariant_attribute = invariant; invariant_attribute = invariant;
} }
if (is_invalid_compute_shader_decoration) {
std::string input_or_output =
param_or_ret == ParamOrRetType::kParameter ? "inputs" : "output";
AddError(
"decoration is not valid for compute shader " + input_or_output,
deco->source());
return false;
}
} }
// Check that we saw a pipeline IO attribute iff we need one. // Check that we saw a pipeline IO attribute iff we need one.
@ -1587,8 +1589,8 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
if (member->Type()->Is<sem::Struct>()) { if (member->Type()->Is<sem::Struct>()) {
AddError("entry point IO types cannot contain nested structures", AddError("entry point IO types cannot contain nested structures",
member->Declaration()->source()); member->Declaration()->source());
AddNote("while analysing entry point " + AddNote("while analysing entry point '" +
builder_->Symbols().NameFor(func->symbol()), builder_->Symbols().NameFor(func->symbol()) + "'",
func->source()); func->source());
return false; return false;
} }
@ -1597,8 +1599,8 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
if (arr->IsRuntimeSized()) { if (arr->IsRuntimeSized()) {
AddError("entry point IO types cannot contain runtime sized arrays", AddError("entry point IO types cannot contain runtime sized arrays",
member->Declaration()->source()); member->Declaration()->source());
AddNote("while analysing entry point " + AddNote("while analysing entry point '" +
builder_->Symbols().NameFor(func->symbol()), builder_->Symbols().NameFor(func->symbol()) + "'",
func->source()); func->source());
return false; return false;
} }
@ -1607,8 +1609,8 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
if (!validate_entry_point_decorations_inner( if (!validate_entry_point_decorations_inner(
member->Declaration()->decorations(), member->Type(), member->Declaration()->decorations(), member->Type(),
member->Declaration()->source(), param_or_ret, true)) { member->Declaration()->source(), param_or_ret, true)) {
AddNote("while analysing entry point " + AddNote("while analysing entry point '" +
builder_->Symbols().NameFor(func->symbol()), builder_->Symbols().NameFor(func->symbol()) + "'",
func->source()); func->source());
return false; return false;
} }
@ -3938,7 +3940,9 @@ bool Resolver::ValidateStructure(const sem::Struct* str) {
invariant_attribute = invariant; invariant_attribute = invariant;
} }
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) { if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
if (!ValidateBuiltinDecoration(builtin, member->Type())) { if (!ValidateBuiltinDecoration(builtin, member->Type(),
/* is_input */ false,
/* is_struct_member */ true)) {
return false; return false;
} }
if (builtin->value() == ast::Builtin::kPosition) { if (builtin->value() == ast::Builtin::kPosition) {

View File

@ -281,7 +281,8 @@ class Resolver {
bool ValidateAssignment(const ast::AssignmentStatement* a); bool ValidateAssignment(const ast::AssignmentStatement* a);
bool ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco, bool ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
const sem::Type* storage_type, const sem::Type* storage_type,
const bool is_input = true); const bool is_input,
const bool is_struct_member);
bool ValidateCallStatement(ast::CallStatement* stmt); bool ValidateCallStatement(ast::CallStatement* stmt);
bool ValidateEntryPoint(const ast::Function* func, const FunctionInfo* info); bool ValidateEntryPoint(const ast::Function* func, const FunctionInfo* info);
bool ValidateFunction(const ast::Function* func, const FunctionInfo* info); bool ValidateFunction(const ast::Function* func, const FunctionInfo* info);