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>
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index 03639c7..9786ff6 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -1143,74 +1143,6 @@
} // namespace
} // 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 {
diff --git a/src/resolver/entry_point_validation_test.cc b/src/resolver/entry_point_validation_test.cc
index 27c4dd6..6b07e91 100644
--- a/src/resolver/entry_point_validation_test.cc
+++ b/src/resolver/entry_point_validation_test.cc
@@ -100,26 +100,6 @@
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) {
// struct Output {
// [[location(0)]] a : f32;
@@ -186,53 +166,6 @@
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) {
// struct Output {
@@ -257,28 +190,6 @@
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) {
// [[stage(fragment)]]
// fn main([[location(0)]] param : f32) {}
@@ -315,24 +226,6 @@
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) {
// struct Input {
// [[location(0)]] a : f32;
@@ -392,51 +285,6 @@
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) {
// [[stage(fragment)]]
// fn main([[builtin(sample_index)]] param_a : u32,
@@ -480,44 +328,6 @@
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) {
// [[stage(vertex)]]
// fn main() {}
@@ -639,6 +449,356 @@
} // 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 resolver
} // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 264b41b..16cbeaf 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1500,19 +1500,11 @@
}
pipeline_io_attribute = deco;
- if (locations.count(location->value())) {
- AddError(deco_to_str(location) +
- " attribute appears multiple times as pipeline " +
- (param_or_ret == ParamOrRetType::kParameter ? "input"
- : "output"),
- func->source());
+ bool is_input = param_or_ret == ParamOrRetType::kParameter;
+ if (!ValidateLocationDecoration(location, ty, locations, source,
+ is_input)) {
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>()) {
if (func->pipeline_stage() == ast::PipelineStage::kCompute) {
is_invalid_compute_shader_decoration = true;
@@ -1535,19 +1527,9 @@
}
}
- // Check that we saw a pipeline IO attribute iff we need one.
- if (ty->Is<sem::Struct>()) {
- if (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) {
+ if (IsValidationEnabled(decos,
+ ast::DisabledValidation::kEntryPointParameter)) {
+ if (!ty->Is<sem::Struct>() && !pipeline_io_attribute) {
std::string err = "missing entry point IO attribute";
if (!is_struct_member) {
err +=
@@ -1558,29 +1540,23 @@
return false;
}
- auto* builtin = pipeline_io_attribute->As<ast::BuiltinDecoration>();
- if (invariant_attribute &&
- !(builtin && builtin->value() == ast::Builtin::kPosition)) {
- AddError(
- "invariant attribute must only be applied to a position builtin",
- invariant_attribute->source());
- return false;
- }
-
- // 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()) {
+ if (invariant_attribute) {
+ bool has_position = false;
+ if (pipeline_io_attribute) {
+ if (auto* builtin =
+ pipeline_io_attribute->As<ast::BuiltinDecoration>()) {
+ has_position = (builtin->value() == ast::Builtin::kPosition);
+ }
+ }
+ if (!has_position) {
AddError(
- "User defined entry point IO types must be a numeric scalar, "
- "a numeric vector, or a structure",
- source);
+ "invariant attribute must only be applied to a position "
+ "builtin",
+ invariant_attribute->source());
return false;
}
}
}
-
return true;
};
@@ -1588,39 +1564,17 @@
auto validate_entry_point_decorations = [&](const ast::DecorationList& decos,
sem::Type* ty, Source source,
ParamOrRetType param_or_ret) {
- // Validate the decorations for the type.
if (!validate_entry_point_decorations_inner(decos, ty, source, param_or_ret,
- false)) {
+ /*is_struct_member*/ false)) {
return false;
}
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()) {
- 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(
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 '" +
builder_->Symbols().NameFor(func->symbol()) + "'",
func->source());
@@ -3920,6 +3874,7 @@
return false;
}
+ std::unordered_set<uint32_t> locations;
for (auto* member : str->Members()) {
if (auto* r = member->Type()->As<sem::Array>()) {
if (r->IsRuntimeSized()) {
@@ -3961,10 +3916,15 @@
deco->source());
return false;
}
+
if (auto* invariant = deco->As<ast::InvariantDecoration>()) {
invariant_attribute = invariant;
- }
- if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
+ } else if (auto* location = deco->As<ast::LocationDecoration>()) {
+ if (!ValidateLocationDecoration(location, member->Type(), locations,
+ member->Declaration()->source())) {
+ return false;
+ }
+ } else if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
if (!ValidateBuiltinDecoration(builtin, member->Type(),
/* is_input */ false,
/* is_struct_member */ true)) {
@@ -4010,6 +3970,42 @@
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) {
if (!ValidateNoDuplicateDecorations(str->decorations())) {
return nullptr;
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 446bac6..86074f2 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -291,6 +291,11 @@
bool ValidateGlobalVariable(const VariableInfo* var);
bool ValidateInterpolateDecoration(const ast::InterpolateDecoration* deco,
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 ValidateFunctionParameter(const ast::Function* func,
const VariableInfo* info);