[validator] Remove requirement to have an entry point

The SPIR-V and HLSL sanitizing transforms add an empty one if
necessary.

Fixed: tint:679
Change-Id: Ic98ff3109d7381b1fbc2de68d95d57e15c7a67c0
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/46700
Commit-Queue: Ben Clayton <bclayton@google.com>
Auto-Submit: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
James Price 2021-04-01 22:07:37 +00:00 committed by Commit Bot service account
parent 1fe1a5e641
commit 2f04dc94ce
9 changed files with 61 additions and 32 deletions

View File

@ -16,6 +16,7 @@
#include <utility> #include <utility>
#include "src/ast/stage_decoration.h"
#include "src/ast/variable_decl_statement.h" #include "src/ast/variable_decl_statement.h"
#include "src/program_builder.h" #include "src/program_builder.h"
#include "src/semantic/expression.h" #include "src/semantic/expression.h"
@ -32,6 +33,7 @@ Transform::Output Hlsl::Run(const Program* in, const DataMap&) {
ProgramBuilder out; ProgramBuilder out;
CloneContext ctx(&out, in); CloneContext ctx(&out, in);
PromoteArrayInitializerToConstVar(ctx); PromoteArrayInitializerToConstVar(ctx);
AddEmptyEntryPoint(ctx);
ctx.Clone(); ctx.Clone();
return Output{Program(std::move(out))}; return Output{Program(std::move(out))};
} }
@ -105,5 +107,16 @@ void Hlsl::PromoteArrayInitializerToConstVar(CloneContext& ctx) const {
} }
} }
void Hlsl::AddEmptyEntryPoint(CloneContext& ctx) const {
for (auto* func : ctx.src->AST().Functions()) {
if (func->IsEntryPoint()) {
return;
}
}
ctx.dst->Func(
"_tint_unused_entry_point", {}, ctx.dst->ty.void_(), {},
{ctx.dst->create<ast::StageDecoration>(ast::PipelineStage::kVertex)});
}
} // namespace transform } // namespace transform
} // namespace tint } // namespace tint

View File

@ -44,6 +44,8 @@ class Hlsl : public Transform {
/// the array usage statement. /// the array usage statement.
/// See crbug.com/tint/406 for more details /// See crbug.com/tint/406 for more details
void PromoteArrayInitializerToConstVar(CloneContext& ctx) const; void PromoteArrayInitializerToConstVar(CloneContext& ctx) const;
/// Add an empty shader entry point if none exist in the module.
void AddEmptyEntryPoint(CloneContext& ctx) const;
}; };
} // namespace transform } // namespace transform

View File

@ -143,6 +143,20 @@ fn main() -> void {
EXPECT_EQ(expect, str(got)); EXPECT_EQ(expect, str(got));
} }
TEST_F(HlslTest, AddEmptyEntryPoint) {
auto* src = R"()";
auto* expect = R"(
[[stage(vertex)]]
fn _tint_unused_entry_point() -> void {
}
)";
auto got = Run<Hlsl>(src);
EXPECT_EQ(expect, str(got));
}
} // namespace } // namespace
} // namespace transform } // namespace transform
} // namespace tint } // namespace tint

View File

@ -19,6 +19,7 @@
#include "src/ast/call_statement.h" #include "src/ast/call_statement.h"
#include "src/ast/return_statement.h" #include "src/ast/return_statement.h"
#include "src/ast/stage_decoration.h"
#include "src/program_builder.h" #include "src/program_builder.h"
#include "src/semantic/function.h" #include "src/semantic/function.h"
#include "src/semantic/statement.h" #include "src/semantic/statement.h"
@ -42,6 +43,7 @@ Transform::Output Spirv::Run(const Program* in, const DataMap&) {
ProgramBuilder out2; ProgramBuilder out2;
CloneContext ctx2(&out2, &tmp); CloneContext ctx2(&out2, &tmp);
HandleSampleMaskBuiltins(ctx2); HandleSampleMaskBuiltins(ctx2);
AddEmptyEntryPoint(ctx2);
ctx2.Clone(); ctx2.Clone();
return Output{Program(std::move(out2))}; return Output{Program(std::move(out2))};
@ -234,6 +236,17 @@ void Spirv::HandleSampleMaskBuiltins(CloneContext& ctx) const {
} }
} }
void Spirv::AddEmptyEntryPoint(CloneContext& ctx) const {
for (auto* func : ctx.src->AST().Functions()) {
if (func->IsEntryPoint()) {
return;
}
}
ctx.dst->Func(
"_tint_unused_entry_point", {}, ctx.dst->ty.void_(), {},
{ctx.dst->create<ast::StageDecoration>(ast::PipelineStage::kCompute)});
}
Symbol Spirv::HoistToInputVariables( Symbol Spirv::HoistToInputVariables(
CloneContext& ctx, CloneContext& ctx,
const ast::Function* func, const ast::Function* func,

View File

@ -47,6 +47,8 @@ class Spirv : public Transform {
void HandleEntryPointIOTypes(CloneContext& ctx) const; void HandleEntryPointIOTypes(CloneContext& ctx) const;
/// Change type of sample mask builtin variables to single element arrays. /// Change type of sample mask builtin variables to single element arrays.
void HandleSampleMaskBuiltins(CloneContext& ctx) const; void HandleSampleMaskBuiltins(CloneContext& ctx) const;
/// Add an empty shader entry point if none exist in the module.
void AddEmptyEntryPoint(CloneContext& ctx) const;
/// Recursively create module-scope input variables for `ty` and add /// Recursively create module-scope input variables for `ty` and add
/// function-scope variables for structs to `func`. /// function-scope variables for structs to `func`.

View File

@ -833,6 +833,20 @@ fn main() -> void {
EXPECT_EQ(expect, str(got)); EXPECT_EQ(expect, str(got));
} }
TEST_F(SpirvTest, AddEmptyEntryPoint) {
auto* src = R"()";
auto* expect = R"(
[[stage(compute)]]
fn _tint_unused_entry_point() -> void {
}
)";
auto got = Run<Spirv>(src);
EXPECT_EQ(expect, str(got));
}
// Test that different transforms within the sanitizer interact correctly. // Test that different transforms within the sanitizer interact correctly.
TEST_F(SpirvTest, MultipleTransforms) { TEST_F(SpirvTest, MultipleTransforms) {
auto* src = R"( auto* src = R"(

View File

@ -78,25 +78,7 @@ TEST_F(ValidateFunctionTest, PipelineStage_MustBeUnique_Fail) {
"12:34 v-0020: only one stage decoration permitted per entry point"); "12:34 v-0020: only one stage decoration permitted per entry point");
} }
TEST_F(ValidateFunctionTest, OnePipelineStageFunctionMustBePresent_Pass) { TEST_F(ValidateFunctionTest, NoPipelineEntryPoints) {
// [[stage(vertex)]]
// fn vtx_func() -> void { return; }
Func("vtx_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::ReturnStatement>(),
},
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
});
ValidatorImpl& v = Build();
EXPECT_TRUE(v.Validate()) << v.error();
}
TEST_F(ValidateFunctionTest, OnePipelineStageFunctionMustBePresent_Fail) {
// fn vtx_func() -> void { return; }
Func("vtx_func", ast::VariableList{}, ty.void_(), Func("vtx_func", ast::VariableList{}, ty.void_(),
ast::StatementList{ ast::StatementList{
create<ast::ReturnStatement>(), create<ast::ReturnStatement>(),
@ -105,10 +87,7 @@ TEST_F(ValidateFunctionTest, OnePipelineStageFunctionMustBePresent_Fail) {
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
EXPECT_FALSE(v.Validate()); EXPECT_TRUE(v.Validate()) << v.error();
EXPECT_EQ(v.error(),
"v-0003: At least one of vertex, fragment or compute shader must "
"be present");
} }
TEST_F(ValidateFunctionTest, FunctionVarInitWithParam) { TEST_F(ValidateFunctionTest, FunctionVarInitWithParam) {

View File

@ -138,10 +138,8 @@ bool ValidatorImpl::ValidateGlobalVariable(const ast::Variable* var) {
} }
bool ValidatorImpl::ValidateEntryPoint(const ast::FunctionList& funcs) { bool ValidatorImpl::ValidateEntryPoint(const ast::FunctionList& funcs) {
auto shader_is_present = false;
for (auto* func : funcs) { for (auto* func : funcs) {
if (func->IsEntryPoint()) { if (func->IsEntryPoint()) {
shader_is_present = true;
auto stage_deco_count = 0; auto stage_deco_count = 0;
for (auto* deco : func->decorations()) { for (auto* deco : func->decorations()) {
if (deco->Is<ast::StageDecoration>()) { if (deco->Is<ast::StageDecoration>()) {
@ -158,12 +156,6 @@ bool ValidatorImpl::ValidateEntryPoint(const ast::FunctionList& funcs) {
} }
} }
} }
if (!shader_is_present) {
add_error(Source{}, "v-0003",
"At least one of vertex, fragment or compute shader must "
"be present");
return false;
}
return true; return true;
} }

View File

@ -65,7 +65,7 @@ TEST_F(ValidatorTest, GlobalConstNoStorageClass_Pass) {
ValidatorImpl& v = Build(); ValidatorImpl& v = Build();
EXPECT_FALSE(v.Validate()) << v.error(); EXPECT_TRUE(v.Validate()) << v.error();
} }
TEST_F(ValidatorTest, GlobalVariableUnique_Pass) { TEST_F(ValidatorTest, GlobalVariableUnique_Pass) {