validation: store type of sample_index and sample_mask builtins must be 'u32'

Bug: tint:506
Change-Id: I786a74640d9ad9d98a76cdbe4dfcbfffdb0ccf2c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/55121
Kokoro: Kokoro <noreply+kokoro@google.com>
Auto-Submit: Sarah Mashayekhi <sarahmashay@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Sarah 2021-06-18 19:58:27 +00:00 committed by Tint LUCI CQ
parent 8b2be2d1e2
commit 443c41de33
4 changed files with 143 additions and 42 deletions

View File

@ -20,31 +20,112 @@ namespace {
class ResolverBuiltinsValidationTest : public resolver::TestHelper, class ResolverBuiltinsValidationTest : public resolver::TestHelper,
public testing::Test {}; public testing::Test {};
TEST_F(ResolverBuiltinsValidationTest, FrontFacingParamIsBool_Pass) { TEST_F(ResolverBuiltinsValidationTest, SampleMaskNotU32_Struct_Fail) {
// struct MyInputs {
// [[builtin(sample_mask)]] m: f32;
// };
// [[stage(fragment)]]
// fn fragShader(is_front: MyInputs) -> [[location(0)]] f32 { return 1.0; }
auto* s = Structure(
"MyInputs", {Member("m", ty.f32(),
ast::DecorationList{Builtin(
Source{{12, 34}}, ast::Builtin::kSampleMask)})});
Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: store type of builtin(sample_mask) must be 'u32'");
}
TEST_F(ResolverBuiltinsValidationTest, SampleMaskIsNotU32_Fail) {
// [[stage(fragment)]] // [[stage(fragment)]]
// fn fs_main( // fn fs_main(
// [[builtin(front_facing)]] is_front: bool // [[builtin(sample_mask)]] arg: bool
// ) -> [[location(0)]] f32 { return 1.0; } // ) -> [[location(0)]] f32 { return 1.0; }
auto* is_front = auto* arg = Param("arg", ty.bool_(),
Param("is_front", ty.bool_(), ast::DecorationList{
ast::DecorationList{Builtin(ast::Builtin::kFrontFacing)}); Builtin(Source{{12, 34}}, ast::Builtin::kSampleMask)});
Func("fs_main", ast::VariableList{is_front}, ty.f32(), {Return(1.0f)}, Func("fs_main", ast::VariableList{arg}, ty.f32(), {Return(1.0f)},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
{Location(0)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: store type of builtin(sample_mask) must be 'u32'");
}
TEST_F(ResolverBuiltinsValidationTest, SampleIndexIsNotU32_Struct_Fail) {
// struct MyInputs {
// [[builtin(sample_index)]] m: f32;
// };
// [[stage(fragment)]]
// fn fragShader(is_front: MyInputs) -> [[location(0)]] f32 { return 1.0; }
auto* s = Structure(
"MyInputs", {Member("m", ty.f32(),
ast::DecorationList{Builtin(
Source{{12, 34}}, ast::Builtin::kSampleIndex)})});
Func("fragShader", {Param("arg", ty.Of(s))}, ty.f32(), {Return(1.0f)},
{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: store type of builtin(sample_index) must be 'u32'");
}
TEST_F(ResolverBuiltinsValidationTest, SampleIndexIsNotU32_Fail) {
// [[stage(fragment)]]
// fn fs_main(
// [[builtin(sample_index)]] arg: bool
// ) -> [[location(0)]] f32 { return 1.0; }
auto* arg = Param("arg", ty.bool_(),
ast::DecorationList{
Builtin(Source{{12, 34}}, ast::Builtin::kSampleIndex)});
Func("fs_main", ast::VariableList{arg}, ty.f32(), {Return(1.0f)},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
{Location(0)});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: store type of builtin(sample_index) must be 'u32'");
}
TEST_F(ResolverBuiltinsValidationTest, FragmentBuiltin_Pass) {
// [[stage(fragment)]]
// fn fs_main(
// [[builtin(front_facing)]] ff : bool,
// [[builtin(sample_index)]] si: u32,
// [[builtin(sample_mask)]] sm : u32
// ) -> [[location(0)]] f32 { return 1.0; }
auto* ff = Param("ff", ty.bool_(),
ast::DecorationList{Builtin(ast::Builtin::kFrontFacing)});
auto* si = Param("si", ty.u32(),
ast::DecorationList{Builtin(ast::Builtin::kSampleIndex)});
auto* sm = Param("sm", ty.u32(),
ast::DecorationList{Builtin(ast::Builtin::kSampleMask)});
Func("fs_main", ast::VariableList{ff, si, sm}, ty.f32(), {Return(1.0f)},
ast::DecorationList{Stage(ast::PipelineStage::kFragment)}, ast::DecorationList{Stage(ast::PipelineStage::kFragment)},
{Location(0)}); {Location(0)});
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
} }
TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsBool_Pass) { TEST_F(ResolverBuiltinsValidationTest, FragmentBuiltinStruct_Pass) {
// struct MyInputs { // Struct MyInputs {
// [[builtin(front_facing)]] pos: bool; // [[builtin(front_facing)]] ff: bool;
// [[builtin(sample_index)]] si: u32;
// [[builtin(sample_mask)]] sm : u32;
// }; // };
// [[stage(fragment)]] // [[stage(fragment)]]
// fn fragShader(is_front: MyInputs) -> [[location(0)]] f32 { return 1.0; } // fn fragShader(is_front: MyInputs) -> [[location(0)]] f32 { return 1.0; }
auto* s = Structure( auto* s = Structure(
"MyInputs", "MyInputs",
{Member("pos", ty.bool_(), {Member("ff", ty.bool_(),
ast::DecorationList{Builtin(ast::Builtin::kFrontFacing)})}); ast::DecorationList{Builtin(ast::Builtin::kFrontFacing)}),
Member("si", ty.u32(),
ast::DecorationList{Builtin(ast::Builtin::kSampleIndex)}),
Member("sm", ty.u32(),
ast::DecorationList{Builtin(ast::Builtin::kSampleMask)})});
Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1.0f)}, Func("fragShader", {Param("is_front", ty.Of(s))}, ty.f32(), {Return(1.0f)},
{Stage(ast::PipelineStage::kFragment)}, {Location(0)}); {Stage(ast::PipelineStage::kFragment)}, {Location(0)});
EXPECT_TRUE(r()->Resolve()) << r()->error(); EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -65,7 +146,7 @@ TEST_F(ResolverBuiltinsValidationTest, FrontFacingParamIsNotBool_Fail) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error v-15001: front_facing builtin must be boolean"); "12:34 error: store type of builtin(front_facing) must be 'bool'");
} }
TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsNotBool_Fail) { TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsNotBool_Fail) {
@ -84,7 +165,7 @@ TEST_F(ResolverBuiltinsValidationTest, FrontFacingMemberIsNotBool_Fail) {
EXPECT_FALSE(r()->Resolve()); EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), EXPECT_EQ(r()->error(),
"12:34 error v-15001: front_facing builtin must be boolean"); "12:34 error: store type of builtin(front_facing) must be 'bool'");
} }
TEST_F(ResolverBuiltinsValidationTest, Length_Float_Scalar) { TEST_F(ResolverBuiltinsValidationTest, Length_Float_Scalar) {

View File

@ -370,7 +370,7 @@ TEST_F(ResolverEntryPointValidationTest,
// fn main(param : Input) {} // fn main(param : Input) {}
auto* input = Structure( auto* input = Structure(
"Input", "Input",
{Member("a", ty.f32(), {Member("a", ty.u32(),
{Location(Source{{13, 43}}, 0), {Location(Source{{13, 43}}, 0),
Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})}); Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})});
auto* param = Param("param", ty.Of(input)); auto* param = Param("param", ty.Of(input));

View File

@ -121,6 +121,16 @@ bool IsValidationDisabled(const ast::DecorationList& decorations,
return false; return false;
} }
// Helper to stringify a pipeline IO decoration.
std::string deco_to_str(const ast::Decoration* deco) {
std::stringstream str;
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
str << "builtin(" << builtin->value() << ")";
} else if (auto* location = deco->As<ast::LocationDecoration>()) {
str << "location(" << location->value() << ")";
}
return str.str();
}
} // namespace } // namespace
Resolver::Resolver(ProgramBuilder* builder) Resolver::Resolver(ProgramBuilder* builder)
@ -891,22 +901,44 @@ bool Resolver::ValidateVariable(const VariableInfo* info) {
} }
bool Resolver::ValidateParameter(const VariableInfo* info) { bool Resolver::ValidateParameter(const VariableInfo* info) {
auto* var = info->declaration; if (!ValidateVariable(info)) {
for (auto* deco : var->decorations()) { return false;
}
for (auto* deco : info->declaration->decorations()) {
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) { if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
if (builtin->value() == ast::Builtin::kFrontFacing) { if (!ValidateBuiltinDecoration(builtin, info->type)) {
auto* storage_type = info->type->UnwrapRef(); return false;
if (!(storage_type->Is<sem::Bool>())) {
diagnostics_.add_error("v-15001",
"front_facing builtin must be boolean",
deco->source());
return false;
}
} }
} }
} }
return true;
}
return ValidateVariable(info); bool Resolver::ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
const sem::Type* storage_type) {
auto* type = storage_type->UnwrapRef();
switch (deco->value()) {
case ast::Builtin::kFrontFacing:
if (!type->Is<sem::Bool>()) {
diagnostics_.add_error(
"store type of " + deco_to_str(deco) + " must be 'bool'",
deco->source());
return false;
}
break;
case ast::Builtin::kSampleMask:
case ast::Builtin::kSampleIndex:
if (!type->Is<sem::U32>()) {
diagnostics_.add_error(
"store type of " + deco_to_str(deco) + " must be 'u32'",
deco->source());
return false;
}
break;
default:
break;
}
return true;
} }
bool Resolver::ValidateFunction(const ast::Function* func, bool Resolver::ValidateFunction(const ast::Function* func,
@ -1014,16 +1046,7 @@ bool Resolver::ValidateEntryPoint(const ast::Function* func,
kParameter, kParameter,
kReturnType, kReturnType,
}; };
// Helper to stringify a pipeline IO decoration.
auto deco_to_str = [](const ast::Decoration* deco) {
std::stringstream str;
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
str << "builtin(" << builtin->value() << ")";
} else if (auto* location = deco->As<ast::LocationDecoration>()) {
str << "location(" << location->value() << ")";
}
return str.str();
};
// Inner lambda that is applied to a type and all of its members. // Inner lambda that is applied to a type and all of its members.
auto validate_entry_point_decorations_inner = auto validate_entry_point_decorations_inner =
[&](const ast::DecorationList& decos, sem::Type* ty, Source source, [&](const ast::DecorationList& decos, sem::Type* ty, Source source,
@ -3178,13 +3201,8 @@ bool Resolver::ValidateStructure(const sem::Struct* str) {
return false; return false;
} }
if (auto* builtin = deco->As<ast::BuiltinDecoration>()) { if (auto* builtin = deco->As<ast::BuiltinDecoration>()) {
if (builtin->value() == ast::Builtin::kFrontFacing) { if (!ValidateBuiltinDecoration(builtin, member->Type())) {
if (!(member->Type()->Is<sem::Bool>())) { return false;
diagnostics_.add_error("v-15001",
"front_facing builtin must be boolean",
deco->source());
return false;
}
} }
} }
} }

View File

@ -266,6 +266,8 @@ class Resolver {
bool ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s); bool ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s);
bool ValidateAtomicUses(); bool ValidateAtomicUses();
bool ValidateAssignment(const ast::AssignmentStatement* a); bool ValidateAssignment(const ast::AssignmentStatement* a);
bool ValidateBuiltinDecoration(const ast::BuiltinDecoration* deco,
const sem::Type* storage_type);
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);