validation: validate 'location' attribute when applied to struct members
- 'location' attribute must only be applied to declarations of numeric scalar or numeric vector type - 'location' attribute is not valid for compute shader - locations must not overlap Bug: tint:1035 Change-Id: I0ba301996f390c8206192d2f81e787e0eac0aa6a Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/59760 Kokoro: Kokoro <noreply+kokoro@google.com> Auto-Submit: Sarah Mashayekhi <sarahmashay@google.com> Commit-Queue: James Price <jrprice@google.com> Reviewed-by: Ben Clayton <bclayton@google.com> Reviewed-by: James Price <jrprice@google.com>
This commit is contained in:
parent
889b77a2a1
commit
c0f1ed4fe7
|
@ -1143,74 +1143,6 @@ 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 InvariantDecorationTests {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
|
@ -100,26 +100,6 @@ TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Multiple) {
|
||||||
13:43 note: previously consumed location(0))");
|
13:43 note: previously consumed location(0))");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Struct) {
|
|
||||||
// struct Output {
|
|
||||||
// a : f32;
|
|
||||||
// };
|
|
||||||
// [[stage(vertex)]]
|
|
||||||
// fn main() -> [[location(0)]] Output {
|
|
||||||
// return Output();
|
|
||||||
// }
|
|
||||||
auto* output = Structure("Output", {Member("a", ty.f32())});
|
|
||||||
Func(Source{{12, 34}}, "main", {}, ty.Of(output),
|
|
||||||
{Return(Construct(ty.Of(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) {
|
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_Valid) {
|
||||||
// struct Output {
|
// struct Output {
|
||||||
// [[location(0)]] a : f32;
|
// [[location(0)]] a : f32;
|
||||||
|
@ -186,53 +166,6 @@ TEST_F(ResolverEntryPointValidationTest,
|
||||||
12:34 note: while analysing entry point 'main')");
|
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", ty.Of(inner), {Location(0)})});
|
|
||||||
Func(Source{{12, 34}}, "main", {}, ty.Of(output),
|
|
||||||
{Return(Construct(ty.Of(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", {}, ty.Of(output),
|
|
||||||
{Return(Construct(ty.Of(output)))},
|
|
||||||
{Stage(ast::PipelineStage::kFragment)});
|
|
||||||
|
|
||||||
EXPECT_FALSE(r()->Resolve());
|
|
||||||
EXPECT_EQ(r()->error(),
|
|
||||||
R"(error: function return type must be a constructible type)");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
|
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
|
||||||
// struct Output {
|
// struct Output {
|
||||||
|
@ -257,28 +190,6 @@ TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
|
||||||
12:34 note: while analysing entry point 'main')");
|
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", {}, ty.Of(output),
|
|
||||||
{Return(Construct(ty.Of(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) {
|
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Location) {
|
||||||
// [[stage(fragment)]]
|
// [[stage(fragment)]]
|
||||||
// fn main([[location(0)]] param : f32) {}
|
// fn main([[location(0)]] param : f32) {}
|
||||||
|
@ -315,24 +226,6 @@ TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Multiple) {
|
||||||
13:43 note: previously consumed location(0))");
|
13:43 note: previously consumed location(0))");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Struct) {
|
|
||||||
// struct Input {
|
|
||||||
// a : f32;
|
|
||||||
// };
|
|
||||||
// [[stage(fragment)]]
|
|
||||||
// fn main([[location(0)]] param : Input) {}
|
|
||||||
auto* input = Structure("Input", {Member("a", ty.f32())});
|
|
||||||
auto* param = Param("param", ty.Of(input), {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) {
|
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_Valid) {
|
||||||
// struct Input {
|
// struct Input {
|
||||||
// [[location(0)]] a : f32;
|
// [[location(0)]] a : f32;
|
||||||
|
@ -392,51 +285,6 @@ TEST_F(ResolverEntryPointValidationTest,
|
||||||
12:34 note: while analysing entry point 'main')");
|
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", ty.Of(inner), {Location(0)})});
|
|
||||||
auto* param = Param("param", ty.Of(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 = Param("param", ty.Of(input));
|
|
||||||
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
|
|
||||||
{Stage(ast::PipelineStage::kFragment)});
|
|
||||||
|
|
||||||
EXPECT_FALSE(r()->Resolve());
|
|
||||||
EXPECT_EQ(
|
|
||||||
r()->error(),
|
|
||||||
R"(error: store type of function parameter must be a constructible type)");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateBuiltins) {
|
TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateBuiltins) {
|
||||||
// [[stage(fragment)]]
|
// [[stage(fragment)]]
|
||||||
// fn main([[builtin(sample_index)]] param_a : u32,
|
// fn main([[builtin(sample_index)]] param_a : u32,
|
||||||
|
@ -480,44 +328,6 @@ TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateBuiltins) {
|
||||||
12:34 note: while analysing entry point 'main')");
|
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 = Param("param_a", ty.u32(), {Location(1)});
|
|
||||||
auto* param_b = Param("param_b", ty.u32(), {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 = Param("param_a", ty.Of(input_a));
|
|
||||||
auto* param_b = Param("param_b", ty.Of(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')");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
|
TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
|
||||||
// [[stage(vertex)]]
|
// [[stage(vertex)]]
|
||||||
// fn main() {}
|
// fn main() {}
|
||||||
|
@ -639,6 +449,356 @@ INSTANTIATE_TEST_SUITE_P(ResolverEntryPointValidationTest,
|
||||||
|
|
||||||
} // namespace TypeValidationTests
|
} // namespace TypeValidationTests
|
||||||
|
|
||||||
|
namespace LocationDecorationTests {
|
||||||
|
namespace {
|
||||||
|
using LocationDecorationTests = ResolverTest;
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, Pass) {
|
||||||
|
// [[stage(fragment)]]
|
||||||
|
// fn frag_main([[location(0)]] a: i32) {}
|
||||||
|
|
||||||
|
auto* p = Param(Source{{12, 34}}, "a", ty.i32(), {Location(0)});
|
||||||
|
Func("frag_main", {p}, ty.void_(), {},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadType_Input_bool) {
|
||||||
|
// [[stage(fragment)]]
|
||||||
|
// fn frag_main([[location(0)]] a: bool) {}
|
||||||
|
|
||||||
|
auto* p =
|
||||||
|
Param(Source{{12, 34}}, "a", ty.bool_(), {Location(Source{{34, 56}}, 0)});
|
||||||
|
Func("frag_main", {p}, ty.void_(), {},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"12:34 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'bool'\n"
|
||||||
|
"34:56 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadType_Output_Array) {
|
||||||
|
// [[stage(fragment)]]
|
||||||
|
// fn frag_main()->[[location(0)]] array<f32, 2> { return array<f32, 2>(); }
|
||||||
|
|
||||||
|
Func(Source{{12, 34}}, "frag_main", {}, ty.array<f32, 2>(),
|
||||||
|
{Return(Construct(ty.array<f32, 2>()))},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)}, {Location(Source{{34, 56}}, 0)});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"12:34 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'array<f32, 2>'\n"
|
||||||
|
"34:56 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadType_Input_Struct) {
|
||||||
|
// struct Input {
|
||||||
|
// a : f32;
|
||||||
|
// };
|
||||||
|
// [[stage(fragment)]]
|
||||||
|
// fn main([[location(0)]] param : Input) {}
|
||||||
|
auto* input = Structure("Input", {Member("a", ty.f32())});
|
||||||
|
auto* param = Param(Source{{12, 34}}, "param", ty.Of(input),
|
||||||
|
{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(),
|
||||||
|
"12:34 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'Input'\n"
|
||||||
|
"13:43 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadType_Input_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", ty.Of(inner),
|
||||||
|
{Location(Source{{12, 34}}, 0)})});
|
||||||
|
auto* param = Param("param", ty.Of(input));
|
||||||
|
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"14:52 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'Inner'\n"
|
||||||
|
"12:34 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadType_Input_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 = Param("param", ty.Of(input));
|
||||||
|
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"13:43 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'array<f32>'\n"
|
||||||
|
"note: 'location' attribute must only be applied to declarations "
|
||||||
|
"of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadMemberType_Input) {
|
||||||
|
// [[block]]
|
||||||
|
// struct S { [[location(0)]] m: array<i32>; };
|
||||||
|
// [[stage(fragment)]]
|
||||||
|
// fn frag_main( a: S) {}
|
||||||
|
|
||||||
|
auto* m = Member(Source{{34, 56}}, "m", ty.array<i32>(),
|
||||||
|
ast::DecorationList{Location(Source{{12, 34}}, 0u)});
|
||||||
|
auto* s = Structure("S", {m}, ast::DecorationList{StructBlock()});
|
||||||
|
auto* p = Param("a", ty.Of(s));
|
||||||
|
|
||||||
|
Func("frag_main", {p}, ty.void_(), {},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"34:56 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'array<i32>'\n"
|
||||||
|
"12:34 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadMemberType_Output) {
|
||||||
|
// struct S { [[location(0)]] m: atomic<i32>; };
|
||||||
|
// [[stage(fragment)]]
|
||||||
|
// fn frag_main() -> S {}
|
||||||
|
auto* m = Member(Source{{34, 56}}, "m", ty.atomic<i32>(),
|
||||||
|
ast::DecorationList{Location(Source{{12, 34}}, 0u)});
|
||||||
|
auto* s = Structure("S", {m});
|
||||||
|
|
||||||
|
Func("frag_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)}, {});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"34:56 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'atomic<i32>'\n"
|
||||||
|
"12:34 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, BadMemberType_Unused) {
|
||||||
|
// struct S { [[location(0)]] m: mat3x2<f32>; };
|
||||||
|
|
||||||
|
auto* m = Member(Source{{34, 56}}, "m", ty.mat3x2<f32>(),
|
||||||
|
ast::DecorationList{Location(Source{{12, 34}}, 0u)});
|
||||||
|
Structure("S", {m});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"34:56 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'mat3x2<f32>'\n"
|
||||||
|
"12:34 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, 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", {}, ty.Of(output),
|
||||||
|
{Return(Construct(ty.Of(output)))},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, ReturnType_Struct) {
|
||||||
|
// struct Output {
|
||||||
|
// a : f32;
|
||||||
|
// };
|
||||||
|
// [[stage(vertex)]]
|
||||||
|
// fn main() -> [[location(0)]] Output {
|
||||||
|
// return Output();
|
||||||
|
// }
|
||||||
|
auto* output = Structure("Output", {Member("a", ty.f32())});
|
||||||
|
Func(Source{{12, 34}}, "main", {}, ty.Of(output),
|
||||||
|
{Return(Construct(ty.Of(output)))}, {Stage(ast::PipelineStage::kVertex)},
|
||||||
|
{Location(Source{{13, 43}}, 0)});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"12:34 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'Output'\n"
|
||||||
|
"13:43 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, ReturnType_Struct_NestedStruct) {
|
||||||
|
// struct Inner {
|
||||||
|
// [[location(0)]] b : f32;
|
||||||
|
// };
|
||||||
|
// struct Output {
|
||||||
|
// [[location(0)]] a : Inner;
|
||||||
|
// };
|
||||||
|
auto* inner = Structure(
|
||||||
|
"Inner", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)})});
|
||||||
|
Structure("Output", {Member(Source{{14, 52}}, "a", ty.Of(inner),
|
||||||
|
{Location(Source{{12, 34}}, 0)})});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"14:52 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'Inner'\n"
|
||||||
|
"12:34 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, 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(Source{{12, 34}}, 0)})},
|
||||||
|
{create<ast::StructBlockDecoration>()});
|
||||||
|
Func(Source{{12, 34}}, "main", {}, ty.Of(output),
|
||||||
|
{Return(Construct(ty.Of(output)))},
|
||||||
|
{Stage(ast::PipelineStage::kFragment)});
|
||||||
|
|
||||||
|
EXPECT_FALSE(r()->Resolve());
|
||||||
|
EXPECT_EQ(r()->error(),
|
||||||
|
"13:43 error: cannot apply 'location' attribute to declaration of "
|
||||||
|
"type 'array<f32>'\n"
|
||||||
|
"12:34 note: 'location' attribute must only be applied to "
|
||||||
|
"declarations of numeric scalar or numeric vector type");
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Duplicate_input) {
|
||||||
|
// [[stage(fragment)]]
|
||||||
|
// fn main([[location(1)]] param_a : f32,
|
||||||
|
// [[location(1)]] param_b : f32) {}
|
||||||
|
auto* param_a = Param("param_a", ty.u32(), {Location(1)});
|
||||||
|
auto* param_b = Param("param_b", ty.u32(), {Location(Source{{12, 34}}, 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LocationDecorationTests, Duplicate_struct) {
|
||||||
|
// 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(Source{{34, 56}}, 1)})});
|
||||||
|
auto* param_a = Param("param_a", ty.Of(input_a));
|
||||||
|
auto* param_b = Param("param_b", ty.Of(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(),
|
||||||
|
"34:56 error: location(1) attribute appears multiple times\n"
|
||||||
|
"12:34 note: while analysing entry point 'main'");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace LocationDecorationTests
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace resolver
|
} // namespace resolver
|
||||||
} // namespace tint
|
} // namespace tint
|
||||||
|
|
|
@ -1500,19 +1500,11 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
||||||
}
|
}
|
||||||
pipeline_io_attribute = deco;
|
pipeline_io_attribute = deco;
|
||||||
|
|
||||||
if (locations.count(location->value())) {
|
bool is_input = param_or_ret == ParamOrRetType::kParameter;
|
||||||
AddError(deco_to_str(location) +
|
if (!ValidateLocationDecoration(location, ty, locations, source,
|
||||||
" attribute appears multiple times as pipeline " +
|
is_input)) {
|
||||||
(param_or_ret == ParamOrRetType::kParameter ? "input"
|
|
||||||
: "output"),
|
|
||||||
func->source());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
|
|
||||||
is_invalid_compute_shader_decoration = true;
|
|
||||||
}
|
|
||||||
locations.emplace(location->value());
|
|
||||||
} else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
|
} else if (auto* interpolate = deco->As<ast::InterpolateDecoration>()) {
|
||||||
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
|
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
|
||||||
is_invalid_compute_shader_decoration = true;
|
is_invalid_compute_shader_decoration = true;
|
||||||
|
@ -1535,19 +1527,9 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that we saw a pipeline IO attribute iff we need one.
|
if (IsValidationEnabled(decos,
|
||||||
if (ty->Is<sem::Struct>()) {
|
ast::DisabledValidation::kEntryPointParameter)) {
|
||||||
if (pipeline_io_attribute) {
|
if (!ty->Is<sem::Struct>() && !pipeline_io_attribute) {
|
||||||
AddError("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 (IsValidationEnabled(
|
|
||||||
decos, ast::DisabledValidation::kEntryPointParameter)) {
|
|
||||||
if (!pipeline_io_attribute) {
|
|
||||||
std::string err = "missing entry point IO attribute";
|
std::string err = "missing entry point IO attribute";
|
||||||
if (!is_struct_member) {
|
if (!is_struct_member) {
|
||||||
err +=
|
err +=
|
||||||
|
@ -1558,29 +1540,23 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* builtin = pipeline_io_attribute->As<ast::BuiltinDecoration>();
|
if (invariant_attribute) {
|
||||||
if (invariant_attribute &&
|
bool has_position = false;
|
||||||
!(builtin && builtin->value() == ast::Builtin::kPosition)) {
|
if (pipeline_io_attribute) {
|
||||||
AddError(
|
if (auto* builtin =
|
||||||
"invariant attribute must only be applied to a position builtin",
|
pipeline_io_attribute->As<ast::BuiltinDecoration>()) {
|
||||||
invariant_attribute->source());
|
has_position = (builtin->value() == ast::Builtin::kPosition);
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
if (!has_position) {
|
||||||
// Check that all user defined attributes are numeric scalars, vectors
|
|
||||||
// of numeric scalars.
|
|
||||||
// Testing for being a struct is handled by the if portion above.
|
|
||||||
if (!builtin) {
|
|
||||||
if (!ty->is_numeric_scalar_or_vector()) {
|
|
||||||
AddError(
|
AddError(
|
||||||
"User defined entry point IO types must be a numeric scalar, "
|
"invariant attribute must only be applied to a position "
|
||||||
"a numeric vector, or a structure",
|
"builtin",
|
||||||
source);
|
invariant_attribute->source());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1588,39 +1564,17 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
|
||||||
auto validate_entry_point_decorations = [&](const ast::DecorationList& decos,
|
auto validate_entry_point_decorations = [&](const ast::DecorationList& decos,
|
||||||
sem::Type* ty, Source source,
|
sem::Type* ty, Source source,
|
||||||
ParamOrRetType param_or_ret) {
|
ParamOrRetType param_or_ret) {
|
||||||
// Validate the decorations for the type.
|
|
||||||
if (!validate_entry_point_decorations_inner(decos, ty, source, param_or_ret,
|
if (!validate_entry_point_decorations_inner(decos, ty, source, param_or_ret,
|
||||||
false)) {
|
/*is_struct_member*/ false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* str = ty->As<sem::Struct>()) {
|
if (auto* str = ty->As<sem::Struct>()) {
|
||||||
// Validate the decorations for each struct members, and also check for
|
|
||||||
// invalid member types.
|
|
||||||
for (auto* member : str->Members()) {
|
for (auto* member : str->Members()) {
|
||||||
if (member->Type()->Is<sem::Struct>()) {
|
|
||||||
AddError("entry point IO types cannot contain nested structures",
|
|
||||||
member->Declaration()->source());
|
|
||||||
AddNote("while analysing entry point '" +
|
|
||||||
builder_->Symbols().NameFor(func->symbol()) + "'",
|
|
||||||
func->source());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto* arr = member->Type()->As<sem::Array>()) {
|
|
||||||
if (arr->IsRuntimeSized()) {
|
|
||||||
AddError("entry point IO types cannot contain runtime sized arrays",
|
|
||||||
member->Declaration()->source());
|
|
||||||
AddNote("while analysing entry point '" +
|
|
||||||
builder_->Symbols().NameFor(func->symbol()) + "'",
|
|
||||||
func->source());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
||||||
|
/*is_struct_member*/ 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());
|
||||||
|
@ -3920,6 +3874,7 @@ bool Resolver::ValidateStructure(const sem::Struct* str) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unordered_set<uint32_t> locations;
|
||||||
for (auto* member : str->Members()) {
|
for (auto* member : str->Members()) {
|
||||||
if (auto* r = member->Type()->As<sem::Array>()) {
|
if (auto* r = member->Type()->As<sem::Array>()) {
|
||||||
if (r->IsRuntimeSized()) {
|
if (r->IsRuntimeSized()) {
|
||||||
|
@ -3961,10 +3916,15 @@ bool Resolver::ValidateStructure(const sem::Struct* str) {
|
||||||
deco->source());
|
deco->source());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
|
if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
|
||||||
invariant_attribute = invariant;
|
invariant_attribute = invariant;
|
||||||
}
|
} else if (auto* location = deco->As<ast::LocationDecoration>()) {
|
||||||
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
|
if (!ValidateLocationDecoration(location, member->Type(), locations,
|
||||||
|
member->Declaration()->source())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
|
||||||
if (!ValidateBuiltinDecoration(builtin, member->Type(),
|
if (!ValidateBuiltinDecoration(builtin, member->Type(),
|
||||||
/* is_input */ false,
|
/* is_input */ false,
|
||||||
/* is_struct_member */ true)) {
|
/* is_struct_member */ true)) {
|
||||||
|
@ -4010,6 +3970,42 @@ bool Resolver::ValidateStructure(const sem::Struct* str) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Resolver::ValidateLocationDecoration(
|
||||||
|
const ast::LocationDecoration* location,
|
||||||
|
const sem::Type* type,
|
||||||
|
std::unordered_set<uint32_t>& locations,
|
||||||
|
const Source& source,
|
||||||
|
const bool is_input) {
|
||||||
|
std::string inputs_or_output = is_input ? "inputs" : "output";
|
||||||
|
if (current_function_ && current_function_->declaration->pipeline_stage() ==
|
||||||
|
ast::PipelineStage::kCompute) {
|
||||||
|
AddError("decoration is not valid for compute shader " + inputs_or_output,
|
||||||
|
location->source());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type->is_numeric_scalar_or_vector()) {
|
||||||
|
std::string invalid_type = type->FriendlyName(builder_->Symbols());
|
||||||
|
AddError("cannot apply 'location' attribute to declaration of type '" +
|
||||||
|
invalid_type + "'",
|
||||||
|
source);
|
||||||
|
AddNote(
|
||||||
|
"'location' attribute must only be applied to declarations of "
|
||||||
|
"numeric scalar or numeric vector type",
|
||||||
|
location->source());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locations.count(location->value())) {
|
||||||
|
AddError(deco_to_str(location) + " attribute appears multiple times",
|
||||||
|
location->source());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
locations.emplace(location->value());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
sem::Struct* Resolver::Structure(const ast::Struct* str) {
|
sem::Struct* Resolver::Structure(const ast::Struct* str) {
|
||||||
if (!ValidateNoDuplicateDecorations(str->decorations())) {
|
if (!ValidateNoDuplicateDecorations(str->decorations())) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -291,6 +291,11 @@ class Resolver {
|
||||||
bool ValidateGlobalVariable(const VariableInfo* var);
|
bool ValidateGlobalVariable(const VariableInfo* var);
|
||||||
bool ValidateInterpolateDecoration(const ast::InterpolateDecoration* deco,
|
bool ValidateInterpolateDecoration(const ast::InterpolateDecoration* deco,
|
||||||
const sem::Type* storage_type);
|
const sem::Type* storage_type);
|
||||||
|
bool ValidateLocationDecoration(const ast::LocationDecoration* location,
|
||||||
|
const sem::Type* type,
|
||||||
|
std::unordered_set<uint32_t>& locations,
|
||||||
|
const Source& source,
|
||||||
|
const bool is_input = false);
|
||||||
bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
|
bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
|
||||||
bool ValidateFunctionParameter(const ast::Function* func,
|
bool ValidateFunctionParameter(const ast::Function* func,
|
||||||
const VariableInfo* info);
|
const VariableInfo* info);
|
||||||
|
|
Loading…
Reference in New Issue