Move function validation from Validator to Resolver

* Fixed many tests that now failed validation. Most of the time,
functions declared that they returned a type, but with no return
statement.
* ProgramBuilder::WrapInFunction now returns the function is creates,
and std::moves its StatementList.
* ProgramBuilder: Added Return function to create ast::ReturnStatements
more easily.

Bug: tint:642
Change-Id: I3011314e66e264ebd7b89bf9271392391be6a0e5
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/45382
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
Antonio Maiorano 2021-03-19 14:04:51 +00:00 committed by Commit Bot service account
parent b4f11f3ff3
commit 03c01b5266
20 changed files with 393 additions and 342 deletions

View File

@ -469,6 +469,7 @@ if(${TINT_BUILD_TESTS})
program_test.cc
resolver/assignment_validation_test.cc
resolver/decoration_validation_test.cc
resolver/function_validation_test.cc
resolver/host_shareable_validation_test.cc
resolver/intrinsic_test.cc
resolver/is_host_shareable_test.cc

View File

@ -97,8 +97,8 @@ ast::Statement* ProgramBuilder::WrapInStatement(ast::Statement* stmt) {
return stmt;
}
void ProgramBuilder::WrapInFunction(ast::StatementList stmts) {
Func("test_function", {}, ty.void_(), stmts, {});
ast::Function* ProgramBuilder::WrapInFunction(ast::StatementList stmts) {
return Func("test_function", {}, ty.void_(), std::move(stmts), {});
}
} // namespace tint

View File

@ -28,6 +28,7 @@
#include "src/ast/loop_statement.h"
#include "src/ast/member_accessor_expression.h"
#include "src/ast/module.h"
#include "src/ast/return_statement.h"
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/sint_literal.h"
#include "src/ast/stride_decoration.h"
@ -1031,6 +1032,14 @@ class ProgramBuilder {
return func;
}
/// Creates an ast::ReturnStatement with the input args
/// @param args arguments to construct a return statement with
/// @returns the return statement pointer
template <typename... Args>
ast::ReturnStatement* Return(Args&&... args) {
return create<ast::ReturnStatement>(std::forward<Args>(args)...);
}
/// Creates a ast::Struct and type::Struct, registering the type::Struct with
/// the AST().ConstructedTypes().
/// @param source the source information
@ -1206,14 +1215,16 @@ class ProgramBuilder {
/// Wraps the list of arguments in a simple function so that each is reachable
/// by the Resolver.
/// @param args a mix of ast::Expression, ast::Statement, ast::Variables.
/// @returns the function
template <typename... ARGS>
void WrapInFunction(ARGS&&... args) {
ast::Function* WrapInFunction(ARGS&&... args) {
ast::StatementList stmts{WrapInStatement(std::forward<ARGS>(args))...};
WrapInFunction(stmts);
return WrapInFunction(std::move(stmts));
}
/// @param stmts a list of ast::Statement that will be wrapped by a function,
/// so that each statement is reachable by the Resolver.
void WrapInFunction(ast::StatementList stmts);
/// @returns the function
ast::Function* WrapInFunction(ast::StatementList stmts);
/// The builder types
TypesBuilder const ty{this};

View File

@ -14,6 +14,7 @@
#include "src/ast/access_decoration.h"
#include "src/ast/constant_id_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"
@ -84,6 +85,41 @@ ast::Decoration* createDecoration(const Source& source,
return nullptr;
}
using FunctionReturnTypeDecorationTest = TestWithParams;
TEST_P(FunctionReturnTypeDecorationTest, IsValid) {
auto params = GetParam();
Func("main", ast::VariableList{}, ty.f32(),
ast::StatementList{create<ast::ReturnStatement>(Expr(1.f))},
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex)},
ast::DecorationList{createDecoration({}, *this, params.kind)});
if (params.should_pass) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"error: decoration is not valid for function return types");
}
}
INSTANTIATE_TEST_SUITE_P(
ResolverDecorationValidationTest,
FunctionReturnTypeDecorationTest,
testing::Values(TestParams{DecorationKind::kAccess, false},
TestParams{DecorationKind::kAlign, false},
TestParams{DecorationKind::kBinding, false},
TestParams{DecorationKind::kBuiltin, true},
TestParams{DecorationKind::kConstantId, false},
TestParams{DecorationKind::kGroup, false},
TestParams{DecorationKind::kLocation, true},
TestParams{DecorationKind::kOffset, false},
TestParams{DecorationKind::kSize, false},
TestParams{DecorationKind::kStage, false},
TestParams{DecorationKind::kStride, false},
TestParams{DecorationKind::kStructBlock, false},
TestParams{DecorationKind::kWorkgroup, false}));
using ArrayDecorationTest = TestWithParams;
TEST_P(ArrayDecorationTest, IsValid) {

View File

@ -0,0 +1,78 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/ast/return_statement.h"
#include "src/resolver/resolver.h"
#include "src/resolver/resolver_test_helper.h"
#include "gmock/gmock.h"
namespace tint {
namespace {
class ResolverFunctionValidationTest : public resolver::TestHelper,
public testing::Test {};
TEST_F(ResolverFunctionValidationTest, FunctionNamesMustBeUnique_fail) {
// fn func -> i32 { return 2; }
// fn func -> i32 { return 2; }
Func("func", ast::VariableList{}, ty.i32(),
ast::StatementList{
create<ast::ReturnStatement>(Expr(2)),
},
ast::DecorationList{});
Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
ast::StatementList{
create<ast::ReturnStatement>(Expr(2)),
},
ast::DecorationList{});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error v-0016: function names must be unique 'func'");
}
TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatement_Fail) {
// fn func -> int { var a:i32 = 2; }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
},
ast::DecorationList{});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error v-0002: non-void function must end with a return statement");
}
TEST_F(ResolverFunctionValidationTest,
FunctionEndWithoutReturnStatementEmptyBody_Fail) {
// fn func -> int {}
Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
ast::StatementList{}, ast::DecorationList{});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error v-0002: non-void function must end with a return statement");
}
} // namespace
} // namespace tint

View File

@ -222,9 +222,65 @@ bool Resolver::Functions(const ast::FunctionList& funcs) {
return true;
}
bool Resolver::ValidateParameter(const ast::Variable* param) {
if (auto* r = param->type()->UnwrapAll()->As<type::Array>()) {
if (r->IsRuntimeArray()) {
diagnostics_.add_error(
"v-0015",
"runtime arrays may only appear as the last member of a struct",
param->source());
return false;
}
}
return true;
}
bool Resolver::ValidateFunction(const ast::Function* func) {
if (symbol_to_function_.find(func->symbol()) != symbol_to_function_.end()) {
diagnostics_.add_error("v-0016",
"function names must be unique '" +
builder_->Symbols().NameFor(func->symbol()) +
"'",
func->source());
return false;
}
for (auto* param : func->params()) {
if (!ValidateParameter(param)) {
return false;
}
}
if (!func->return_type()->Is<type::Void>()) {
if (!func->get_last_statement() ||
!func->get_last_statement()->Is<ast::ReturnStatement>()) {
diagnostics_.add_error(
"v-0002", "non-void function must end with a return statement",
func->source());
return false;
}
for (auto* deco : func->return_type_decorations()) {
if (!(deco->Is<ast::BuiltinDecoration>() ||
deco->Is<ast::LocationDecoration>())) {
diagnostics_.add_error(
"decoration is not valid for function return types",
deco->source());
return false;
}
}
}
return true;
}
bool Resolver::Function(ast::Function* func) {
auto* func_info = function_infos_.Create<FunctionInfo>(func);
if (!ValidateFunction(func)) {
return false;
}
ScopedAssignment<FunctionInfo*> sa(current_function_, func_info);
variable_stack_.push_scope();

View File

@ -224,16 +224,15 @@ class Resolver {
// AST and Type validation methods
// Each return true on success, false on failure.
bool ValidateBinary(ast::BinaryExpression* expr);
bool ValidateParameter(const ast::Variable* param);
bool ValidateFunction(const ast::Function* func);
bool ValidateStructure(const type::Struct* st);
/// @returns the semantic information for the array `arr`, building it if it
/// hasn't been constructed already. If an error is raised, nullptr is
/// returned.
const semantic::Array* Array(type::Array*);
/// @returns returns true if input struct is valid
/// @param st the struct to validate
bool ValidateStructure(const type::Struct* st);
/// @returns the StructInfo for the structure `str`, building it if it hasn't
/// been constructed already. If an error is raised, nullptr is returned.
StructInfo* Structure(type::Struct* str);
@ -286,7 +285,7 @@ class Resolver {
BlockInfo* current_block_ = nullptr;
ScopeStack<VariableInfo*> variable_stack_;
std::unordered_map<Symbol, FunctionInfo*> symbol_to_function_;
std::unordered_map<ast::Function*, FunctionInfo*> function_to_info_;
std::unordered_map<const ast::Function*, FunctionInfo*> function_to_info_;
std::unordered_map<ast::Variable*, VariableInfo*> variable_to_info_;
std::unordered_map<ast::CallExpression*, FunctionCallInfo> function_calls_;
std::unordered_map<ast::Expression*, ExpressionInfo> expr_info_;

View File

@ -271,7 +271,7 @@ TEST_F(ResolverTest, Stmt_Switch) {
TEST_F(ResolverTest, Stmt_Call) {
ast::VariableList params;
Func("my_func", params, ty.f32(), ast::StatementList{},
Func("my_func", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
ast::DecorationList{});
auto* expr = Call("my_func");
@ -325,7 +325,7 @@ TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScope) {
}
TEST_F(ResolverTest, Stmt_VariableDecl_OuterScopeAfterInnerScope) {
// fn func_i32() -> i32 {
// fn func_i32() -> void {
// {
// var foo : i32 = 2;
// var bar : i32 = foo;
@ -359,11 +359,11 @@ TEST_F(ResolverTest, Stmt_VariableDecl_OuterScopeAfterInnerScope) {
auto* bar_f32_init = bar_f32->constructor();
auto* bar_f32_decl = create<ast::VariableDeclStatement>(bar_f32);
Func("func", params, ty.f32(),
Func("func", params, ty.void_(),
ast::StatementList{inner, foo_f32_decl, bar_f32_decl},
ast::DecorationList{});
EXPECT_TRUE(r()->Resolve());
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(foo_i32_init), nullptr);
EXPECT_TRUE(TypeOf(foo_i32_init)->Is<type::I32>());
ASSERT_NE(TypeOf(foo_f32_init), nullptr);
@ -381,11 +381,11 @@ TEST_F(ResolverTest, Stmt_VariableDecl_OuterScopeAfterInnerScope) {
}
TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScopeAfterFunctionScope) {
// fn func_i32() -> i32 {
// fn func_i32() -> void {
// var foo : i32 = 2;
// }
// var foo : f32 = 2.0;
// fn func_f32() -> f32 {
// fn func_f32() -> void {
// var bar : f32 = foo;
// }
@ -395,7 +395,7 @@ TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScopeAfterFunctionScope) {
auto* fn_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2));
auto* fn_i32_init = fn_i32->constructor();
auto* fn_i32_decl = create<ast::VariableDeclStatement>(fn_i32);
Func("func_i32", params, ty.i32(), ast::StatementList{fn_i32_decl},
Func("func_i32", params, ty.void_(), ast::StatementList{fn_i32_decl},
ast::DecorationList{});
// Declare f32 "foo" at module scope
@ -407,10 +407,10 @@ TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScopeAfterFunctionScope) {
auto* fn_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
auto* fn_f32_init = fn_f32->constructor();
auto* fn_f32_decl = create<ast::VariableDeclStatement>(fn_f32);
Func("func_f32", params, ty.f32(), ast::StatementList{fn_f32_decl},
Func("func_f32", params, ty.void_(), ast::StatementList{fn_f32_decl},
ast::DecorationList{});
EXPECT_TRUE(r()->Resolve());
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(mod_init), nullptr);
EXPECT_TRUE(TypeOf(mod_init)->Is<type::F32>());
ASSERT_NE(TypeOf(fn_i32_init), nullptr);
@ -529,7 +529,7 @@ TEST_F(ResolverTest, Expr_Bitcast) {
TEST_F(ResolverTest, Expr_Call) {
ast::VariableList params;
Func("my_func", params, ty.f32(), ast::StatementList{},
Func("my_func", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
ast::DecorationList{});
auto* call = Call("my_func");
@ -543,7 +543,8 @@ TEST_F(ResolverTest, Expr_Call) {
TEST_F(ResolverTest, Expr_Call_InBinaryOp) {
ast::VariableList params;
Func("func", params, ty.f32(), ast::StatementList{}, ast::DecorationList{});
Func("func", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
ast::DecorationList{});
auto* expr = Add(Call("func"), Call("func"));
WrapInFunction(expr);
@ -556,7 +557,7 @@ TEST_F(ResolverTest, Expr_Call_InBinaryOp) {
TEST_F(ResolverTest, Expr_Call_WithParams) {
ast::VariableList params;
Func("my_func", params, ty.f32(), ast::StatementList{},
Func("my_func", params, ty.void_(), ast::StatementList{},
ast::DecorationList{});
auto* param = Expr(2.4f);
@ -671,7 +672,7 @@ TEST_F(ResolverTest, Expr_Identifier_FunctionVariable_Const) {
auto* var = Const("my_var", ty.f32());
auto* assign = create<ast::AssignmentStatement>(my_var_a, my_var_b);
Func("my_func", ast::VariableList{}, ty.f32(),
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
assign,
@ -696,7 +697,7 @@ TEST_F(ResolverTest, Expr_Identifier_FunctionVariable) {
auto* var = Var("my_var", ty.f32(), ast::StorageClass::kNone);
Func("my_func", ast::VariableList{}, ty.f32(),
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
assign,
@ -721,7 +722,7 @@ TEST_F(ResolverTest, Expr_Identifier_Function_Ptr) {
auto* my_var_b = Expr("my_var");
auto* assign = create<ast::AssignmentStatement>(my_var_a, my_var_b);
Func("my_func", ast::VariableList{}, ty.f32(),
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::VariableDeclStatement>(
Var("my_var", ty.pointer<f32>(ast::StorageClass::kFunction),
@ -743,8 +744,8 @@ TEST_F(ResolverTest, Expr_Identifier_Function_Ptr) {
}
TEST_F(ResolverTest, Expr_Call_Function) {
Func("my_func", ast::VariableList{}, ty.f32(), ast::StatementList{},
ast::DecorationList{});
Func("my_func", ast::VariableList{}, ty.f32(),
ast::StatementList{Return(Expr(0.0f))}, ast::DecorationList{});
auto* call = Call("my_func");
WrapInFunction(call);
@ -770,7 +771,7 @@ TEST_F(ResolverTest, Function_RegisterInputOutputVariables) {
auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
auto* func = Func(
"my_func", ast::VariableList{}, ty.f32(),
"my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("out_var"), Expr("in_var")),
create<ast::AssignmentStatement>(Expr("wg_var"), Expr("wg_var")),
@ -806,11 +807,11 @@ TEST_F(ResolverTest, Function_RegisterInputOutputVariables_SubFunction) {
create<ast::AssignmentStatement>(Expr("wg_var"), Expr("wg_var")),
create<ast::AssignmentStatement>(Expr("sb_var"), Expr("sb_var")),
create<ast::AssignmentStatement>(Expr("priv_var"), Expr("priv_var")),
},
Return(Expr(0.0f))},
ast::DecorationList{});
auto* func2 = Func(
"func", ast::VariableList{}, ty.f32(),
"func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("out_var"), Call("my_func")),
},
@ -834,7 +835,7 @@ TEST_F(ResolverTest, Function_NotRegisterFunctionVariable) {
auto* var = Var("in_var", ty.f32(), ast::StorageClass::kFunction);
auto* func =
Func("my_func", ast::VariableList{}, ty.f32(),
Func("my_func", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
create<ast::AssignmentStatement>(Expr("var"), Expr(1.f)),
@ -1276,7 +1277,7 @@ TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
auto* var = Var("var", ty.i32(), ast::StorageClass::kNone);
auto* stmt = create<ast::VariableDeclStatement>(var);
Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
ast::DecorationList{});
EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -1287,7 +1288,7 @@ TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
TEST_F(ResolverTest, StorageClass_DoesNotSetOnConst) {
auto* var = Const("var", ty.i32());
auto* stmt = create<ast::VariableDeclStatement>(var);
Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
ast::DecorationList{});
EXPECT_TRUE(r()->Resolve()) << r()->error();
@ -1310,23 +1311,22 @@ TEST_F(ResolverTest, Function_EntryPoints_StageDecoration) {
ast::VariableList params;
auto* func_b =
Func("b", params, ty.f32(), ast::StatementList{}, ast::DecorationList{});
auto* func_c =
Func("c", params, ty.f32(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("second"), Call("b")),
},
Func("b", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
ast::DecorationList{});
auto* func_c = Func("c", params, ty.f32(),
ast::StatementList{create<ast::AssignmentStatement>(
Expr("second"), Call("b")),
Return(Expr(0.0f))},
ast::DecorationList{});
auto* func_a =
Func("a", params, ty.f32(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("first"), Call("c")),
},
ast::DecorationList{});
auto* func_a = Func("a", params, ty.f32(),
ast::StatementList{create<ast::AssignmentStatement>(
Expr("first"), Call("c")),
Return(Expr(0.0f))},
ast::DecorationList{});
auto* ep_1 =
Func("ep_1", params, ty.f32(),
Func("ep_1", params, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("call_a"), Call("a")),
create<ast::AssignmentStatement>(Expr("call_b"), Call("b")),
@ -1336,7 +1336,7 @@ TEST_F(ResolverTest, Function_EntryPoints_StageDecoration) {
});
auto* ep_2 =
Func("ep_2", params, ty.f32(),
Func("ep_2", params, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("call_c"), Call("c")),
},

View File

@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/ast/return_statement.h"
#include "src/ast/stage_decoration.h"
#include "src/ast/struct_block_decoration.h"
#include "src/resolver/resolver.h"
@ -96,6 +97,34 @@ TEST_F(ResolverTypeValidationTest, RuntimeArrayIsNotLast_Fail) {
"member of a struct");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayAsParameter_Fail) {
// fn func(a : array<u32>) {}
// [[stage(vertex)]] fn main() {}
auto* param = Var(Source{Source::Location{12, 34}}, "a", ty.array<i32>(),
ast::StorageClass::kNone);
Func("func", ast::VariableList{param}, ty.void_(),
ast::StatementList{
create<ast::ReturnStatement>(),
},
ast::DecorationList{});
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::ReturnStatement>(),
},
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
});
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(
r()->error(),
"12:34 error v-0015: runtime arrays may only appear as the last member "
"of a struct");
}
TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
// [[Block]]
// type RTArr = array<u32>;

View File

@ -116,7 +116,7 @@ TEST_F(ResolverValidationTest, Stmt_Call_recursive) {
auto* call_expr = Call("main");
ast::VariableList params0;
Func("main", params0, ty.f32(),
Func("main", params0, ty.void_(),
ast::StatementList{
create<ast::CallStatement>(call_expr),
},
@ -245,7 +245,7 @@ TEST_F(ResolverValidationTest, StorageClass_NonFunctionClassError) {
auto* var = Var("var", ty.i32(), ast::StorageClass::kWorkgroup);
auto* stmt = create<ast::VariableDeclStatement>(var);
Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
ast::DecorationList{});
EXPECT_FALSE(r()->Resolve());

View File

@ -121,42 +121,6 @@ INSTANTIATE_TEST_SUITE_P(
DecorationTestParams{DecorationKind::kStructBlock, false},
DecorationTestParams{DecorationKind::kWorkgroup, true}));
using FunctionReturnTypeDecorationTest = ValidatorDecorationsTestWithParams;
TEST_P(FunctionReturnTypeDecorationTest, Decoration_IsValid) {
auto params = GetParam();
Func("main", ast::VariableList{}, ty.f32(),
ast::StatementList{create<ast::ReturnStatement>(Expr(1.f))},
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex)},
ast::DecorationList{createDecoration(*this, params.kind)});
ValidatorImpl& v = Build();
if (params.should_pass) {
EXPECT_TRUE(v.Validate());
} else {
EXPECT_FALSE(v.Validate());
EXPECT_EQ(v.error(), "decoration is not valid for function return types");
}
}
INSTANTIATE_TEST_SUITE_P(
ValidatorTest,
FunctionReturnTypeDecorationTest,
testing::Values(DecorationTestParams{DecorationKind::kAccess, false},
DecorationTestParams{DecorationKind::kAlign, false},
DecorationTestParams{DecorationKind::kBinding, false},
DecorationTestParams{DecorationKind::kBuiltin, true},
DecorationTestParams{DecorationKind::kConstantId, false},
DecorationTestParams{DecorationKind::kGroup, false},
DecorationTestParams{DecorationKind::kLocation, true},
DecorationTestParams{DecorationKind::kOffset, false},
DecorationTestParams{DecorationKind::kSize, false},
DecorationTestParams{DecorationKind::kStage, false},
DecorationTestParams{DecorationKind::kStride, false},
DecorationTestParams{DecorationKind::kStructBlock, false},
DecorationTestParams{DecorationKind::kWorkgroup, false}));
using VariableDecorationTest = ValidatorDecorationsTestWithParams;
TEST_P(VariableDecorationTest, Decoration_IsValid) {
auto params = GetParam();

View File

@ -56,37 +56,6 @@ TEST_F(ValidateFunctionTest,
EXPECT_TRUE(v.Validate());
}
TEST_F(ValidateFunctionTest, FunctionEndWithoutReturnStatement_Fail) {
// fn func -> int { var a:i32 = 2; }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
ast::StatementList{
create<ast::VariableDeclStatement>(var),
},
ast::DecorationList{});
ValidatorImpl& v = Build();
EXPECT_FALSE(v.Validate());
EXPECT_EQ(v.error(),
"12:34 v-0002: non-void function must end with a return statement");
}
TEST_F(ValidateFunctionTest, FunctionEndWithoutReturnStatementEmptyBody_Fail) {
// fn func -> int {}
Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
ast::StatementList{}, ast::DecorationList{});
ValidatorImpl& v = Build();
EXPECT_FALSE(v.Validate());
EXPECT_EQ(v.error(),
"12:34 v-0002: non-void function must end with a return statement");
}
TEST_F(ValidateFunctionTest, FunctionTypeMustMatchReturnStatementType_Pass) {
// [[stage(vertex)]]
// fn func -> void { return; }
@ -204,26 +173,6 @@ TEST_F(ValidateFunctionTest,
"return type, returned '__u32', expected '__alias_tint_symbol_1__f32'");
}
TEST_F(ValidateFunctionTest, FunctionNamesMustBeUnique_fail) {
// fn func -> i32 { return 2; }
// fn func -> i32 { return 2; }
Func("func", ast::VariableList{}, ty.i32(),
ast::StatementList{
create<ast::ReturnStatement>(Expr(2)),
},
ast::DecorationList{});
Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
ast::StatementList{
create<ast::ReturnStatement>(Expr(2)),
},
ast::DecorationList{});
ValidatorImpl& v = Build();
EXPECT_FALSE(v.Validate());
EXPECT_EQ(v.error(), "12:34 v-0016: function names must be unique 'func'");
}
TEST_F(ValidateFunctionTest, PipelineStage_MustBeUnique_Fail) {
// [[stage(fragment)]]

View File

@ -168,57 +168,11 @@ bool ValidatorImpl::ValidateEntryPoint(const ast::FunctionList& funcs) {
}
bool ValidatorImpl::ValidateFunction(const ast::Function* func) {
if (function_stack_.has(func->symbol())) {
add_error(func->source(), "v-0016",
"function names must be unique '" +
program_->Symbols().NameFor(func->symbol()) + "'");
return false;
}
function_stack_.set(func->symbol(), func);
variable_stack_.push_scope();
for (auto* param : func->params()) {
variable_stack_.set(param->symbol(), param);
if (!ValidateParameter(param)) {
return false;
}
}
// TODO(amaiorano): Remove ValidateFunction once we've moved all the statement
// validation to Resovler
if (!ValidateStatements(func->body())) {
return false;
}
variable_stack_.pop_scope();
if (!current_function_->return_type()->Is<type::Void>()) {
if (!func->get_last_statement() ||
!func->get_last_statement()->Is<ast::ReturnStatement>()) {
add_error(func->source(), "v-0002",
"non-void function must end with a return statement");
return false;
}
for (auto* deco : current_function_->return_type_decorations()) {
if (!(deco->Is<ast::BuiltinDecoration>() ||
deco->Is<ast::LocationDecoration>())) {
add_error(deco->source(),
"decoration is not valid for function return types");
return false;
}
}
}
return true;
}
bool ValidatorImpl::ValidateParameter(const ast::Variable* param) {
if (auto* r = param->type()->UnwrapAll()->As<type::Array>()) {
if (r->IsRuntimeArray()) {
add_error(
param->source(), "v-0015",
"runtime arrays may only appear as the last member of a struct");
return false;
}
}
return true;
}

View File

@ -76,10 +76,6 @@ class ValidatorImpl {
/// @param func the function to check
/// @returns true if the validation was successful
bool ValidateFunction(const ast::Function* func);
/// Validates a function parameter
/// @param param the function parameter to check
/// @returns true if the validation was successful
bool ValidateParameter(const ast::Variable* param);
/// Validates a block of statements
/// @param block the statements to check
/// @returns true if the validation was successful
@ -147,7 +143,6 @@ class ValidatorImpl {
const Program* program_;
diag::List diags_;
ScopeStack<const ast::Variable*> variable_stack_;
ScopeStack<const ast::Function*> function_stack_;
ast::Function* current_function_ = nullptr;
};

View File

@ -44,33 +44,5 @@ TEST_F(ValidatorTypeTest, RuntimeArrayInFunction_Fail) {
"of a struct");
}
TEST_F(ValidatorTypeTest, RuntimeArrayAsParameter_Fail) {
// fn func(a : array<u32>) {}
// [[stage(vertex)]] fn main() {}
auto* param = Var(Source{Source::Location{12, 34}}, "a", ty.array<i32>(),
ast::StorageClass::kNone);
Func("func", ast::VariableList{param}, ty.void_(),
ast::StatementList{
create<ast::ReturnStatement>(),
},
ast::DecorationList{});
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::ReturnStatement>(),
},
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
});
ValidatorImpl& v = Build();
EXPECT_FALSE(v.Validate());
EXPECT_EQ(v.error(),
"12:34 v-0015: runtime arrays may only appear as the last member "
"of a struct");
}
} // namespace
} // namespace tint

View File

@ -42,7 +42,7 @@ TEST_F(HlslGeneratorImplTest_EntryPoint,
create<ast::LocationDecoration>(1),
});
Func("vtx_main", ast::VariableList{}, ty.f32(),
Func("vtx_main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@ -85,7 +85,7 @@ TEST_F(HlslGeneratorImplTest_EntryPoint,
create<ast::LocationDecoration>(1),
});
Func("vtx_main", ast::VariableList{}, ty.f32(),
Func("vtx_main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@ -128,7 +128,7 @@ TEST_F(HlslGeneratorImplTest_EntryPoint,
create<ast::LocationDecoration>(1),
});
Func("main", ast::VariableList{}, ty.f32(),
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@ -171,7 +171,7 @@ TEST_F(HlslGeneratorImplTest_EntryPoint,
create<ast::LocationDecoration>(1),
});
Func("main", ast::VariableList{}, ty.f32(),
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@ -211,7 +211,7 @@ TEST_F(HlslGeneratorImplTest_EntryPoint,
create<ast::LocationDecoration>(1),
});
Func("main", ast::VariableList{}, ty.f32(),
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@ -247,7 +247,7 @@ TEST_F(HlslGeneratorImplTest_EntryPoint,
create<ast::LocationDecoration>(1),
});
Func("main", ast::VariableList{}, ty.f32(),
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),

View File

@ -42,7 +42,7 @@ TEST_F(MslGeneratorImplTest, Emit_Function_EntryPointData_Vertex_Input) {
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
};
Func("vtx_main", ast::VariableList{}, ty.f32(), body,
Func("vtx_main", ast::VariableList{}, ty.void_(), body,
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
});
@ -79,7 +79,7 @@ TEST_F(MslGeneratorImplTest, Emit_Function_EntryPointData_Vertex_Output) {
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
};
Func("vtx_main", ast::VariableList{}, ty.f32(), body,
Func("vtx_main", ast::VariableList{}, ty.void_(), body,
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kVertex),
});
@ -116,7 +116,7 @@ TEST_F(MslGeneratorImplTest, Emit_Function_EntryPointData_Fragment_Input) {
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
};
Func("main", ast::VariableList{}, ty.f32(), body,
Func("main", ast::VariableList{}, ty.void_(), body,
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kFragment),
});
@ -153,7 +153,7 @@ TEST_F(MslGeneratorImplTest, Emit_Function_EntryPointData_Fragment_Output) {
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
};
Func("main", ast::VariableList{}, ty.f32(), body,
Func("main", ast::VariableList{}, ty.void_(), body,
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kFragment),
});
@ -187,7 +187,7 @@ TEST_F(MslGeneratorImplTest, Emit_Function_EntryPointData_Compute_Input) {
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
};
Func("main", ast::VariableList{}, ty.f32(), body,
Func("main", ast::VariableList{}, ty.void_(), body,
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kCompute),
});
@ -217,7 +217,7 @@ TEST_F(MslGeneratorImplTest, Emit_Function_EntryPointData_Compute_Output) {
create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
};
Func("main", ast::VariableList{}, ty.f32(), body,
Func("main", ast::VariableList{}, ty.void_(), body,
ast::DecorationList{
create<ast::StageDecoration>(ast::PipelineStage::kCompute),
});

View File

@ -382,8 +382,8 @@ TEST_F(IntrinsicBuilderTest, Call_TextureSampleCompare_Twice) {
auto* expr2 = Call("textureSampleCompare", "texture", "sampler",
vec2<f32>(1.0f, 2.0f), 2.0f);
WrapInFunction(expr1);
WrapInFunction(expr2);
Func("f1", {}, ty.void_(), {create<ast::CallStatement>(expr1)}, {});
Func("f2", {}, ty.void_(), {create<ast::CallStatement>(expr2)}, {});
spirv::Builder& b = Build();

View File

@ -79,8 +79,8 @@ TEST_F(BuilderTest, Switch_WithCase) {
WrapInFunction(expr);
auto* func =
Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
ast::DecorationList{});
spirv::Builder& b = Build();
@ -92,29 +92,30 @@ TEST_F(BuilderTest, Switch_WithCase) {
EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
OpName %5 "a"
OpName %7 "a_func"
OpName %8 "a_func"
%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%4 = OpConstantNull %3
%1 = OpVariable %2 Private %4
%5 = OpVariable %2 Private %4
%6 = OpTypeFunction %3
%14 = OpConstant %3 1
%15 = OpConstant %3 2
%7 = OpFunction %3 None %6
%8 = OpLabel
%10 = OpLoad %3 %5
OpSelectionMerge %9 None
OpSwitch %10 %11 1 %12 2 %13
%12 = OpLabel
OpStore %1 %14
OpBranch %9
%7 = OpTypeVoid
%6 = OpTypeFunction %7
%15 = OpConstant %3 1
%16 = OpConstant %3 2
%8 = OpFunction %7 None %6
%9 = OpLabel
%11 = OpLoad %3 %5
OpSelectionMerge %10 None
OpSwitch %11 %12 1 %13 2 %14
%13 = OpLabel
OpStore %1 %15
OpBranch %9
%11 = OpLabel
OpBranch %9
%9 = OpLabel
OpBranch %10
%14 = OpLabel
OpStore %1 %16
OpBranch %10
%12 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)");
@ -151,8 +152,8 @@ TEST_F(BuilderTest, Switch_WithCase_Unsigned) {
WrapInFunction(expr);
auto* func =
Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
ast::DecorationList{});
spirv::Builder& b = Build();
@ -164,7 +165,7 @@ TEST_F(BuilderTest, Switch_WithCase_Unsigned) {
EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
OpName %5 "a"
OpName %10 "a_func"
OpName %11 "a_func"
%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%4 = OpConstantNull %3
@ -173,23 +174,24 @@ OpName %10 "a_func"
%6 = OpTypePointer Private %7
%8 = OpConstantNull %7
%5 = OpVariable %6 Private %8
%9 = OpTypeFunction %3
%17 = OpConstant %3 1
%18 = OpConstant %3 2
%10 = OpFunction %3 None %9
%11 = OpLabel
%13 = OpLoad %7 %5
OpSelectionMerge %12 None
OpSwitch %13 %14 1 %15 2 %16
%15 = OpLabel
OpStore %1 %17
OpBranch %12
%10 = OpTypeVoid
%9 = OpTypeFunction %10
%18 = OpConstant %3 1
%19 = OpConstant %3 2
%11 = OpFunction %10 None %9
%12 = OpLabel
%14 = OpLoad %7 %5
OpSelectionMerge %13 None
OpSwitch %14 %15 1 %16 2 %17
%16 = OpLabel
OpStore %1 %18
OpBranch %12
%14 = OpLabel
OpBranch %12
%12 = OpLabel
OpBranch %13
%17 = OpLabel
OpStore %1 %19
OpBranch %13
%15 = OpLabel
OpBranch %13
%13 = OpLabel
OpReturn
OpFunctionEnd
)");
@ -215,8 +217,8 @@ TEST_F(BuilderTest, Switch_WithDefault) {
WrapInFunction(expr);
auto* func =
Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
ast::DecorationList{});
spirv::Builder& b = Build();
@ -228,23 +230,24 @@ TEST_F(BuilderTest, Switch_WithDefault) {
EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
OpName %5 "a"
OpName %7 "a_func"
OpName %8 "a_func"
%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%4 = OpConstantNull %3
%1 = OpVariable %2 Private %4
%5 = OpVariable %2 Private %4
%6 = OpTypeFunction %3
%12 = OpConstant %3 1
%7 = OpFunction %3 None %6
%8 = OpLabel
%10 = OpLoad %3 %5
OpSelectionMerge %9 None
OpSwitch %10 %11
%11 = OpLabel
OpStore %1 %12
OpBranch %9
%7 = OpTypeVoid
%6 = OpTypeFunction %7
%13 = OpConstant %3 1
%8 = OpFunction %7 None %6
%9 = OpLabel
%11 = OpLoad %3 %5
OpSelectionMerge %10 None
OpSwitch %11 %12
%12 = OpLabel
OpStore %1 %13
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)");
@ -289,8 +292,8 @@ TEST_F(BuilderTest, Switch_WithCaseAndDefault) {
WrapInFunction(expr);
auto* func =
Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
ast::DecorationList{});
spirv::Builder& b = Build();
@ -302,31 +305,32 @@ TEST_F(BuilderTest, Switch_WithCaseAndDefault) {
EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
OpName %5 "a"
OpName %7 "a_func"
OpName %8 "a_func"
%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%4 = OpConstantNull %3
%1 = OpVariable %2 Private %4
%5 = OpVariable %2 Private %4
%6 = OpTypeFunction %3
%14 = OpConstant %3 1
%15 = OpConstant %3 2
%16 = OpConstant %3 3
%7 = OpFunction %3 None %6
%8 = OpLabel
%10 = OpLoad %3 %5
OpSelectionMerge %9 None
OpSwitch %10 %11 1 %12 2 %13 3 %13
%12 = OpLabel
OpStore %1 %14
OpBranch %9
%7 = OpTypeVoid
%6 = OpTypeFunction %7
%15 = OpConstant %3 1
%16 = OpConstant %3 2
%17 = OpConstant %3 3
%8 = OpFunction %7 None %6
%9 = OpLabel
%11 = OpLoad %3 %5
OpSelectionMerge %10 None
OpSwitch %11 %12 1 %13 2 %14 3 %14
%13 = OpLabel
OpStore %1 %15
OpBranch %9
%11 = OpLabel
OpBranch %10
%14 = OpLabel
OpStore %1 %16
OpBranch %9
%9 = OpLabel
OpBranch %10
%12 = OpLabel
OpStore %1 %17
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)");
@ -372,8 +376,8 @@ TEST_F(BuilderTest, Switch_CaseWithFallthrough) {
WrapInFunction(expr);
auto* func =
Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
ast::DecorationList{});
spirv::Builder& b = Build();
@ -385,31 +389,32 @@ TEST_F(BuilderTest, Switch_CaseWithFallthrough) {
EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
OpName %5 "a"
OpName %7 "a_func"
OpName %8 "a_func"
%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%4 = OpConstantNull %3
%1 = OpVariable %2 Private %4
%5 = OpVariable %2 Private %4
%6 = OpTypeFunction %3
%14 = OpConstant %3 1
%15 = OpConstant %3 2
%16 = OpConstant %3 3
%7 = OpFunction %3 None %6
%8 = OpLabel
%10 = OpLoad %3 %5
OpSelectionMerge %9 None
OpSwitch %10 %11 1 %12 2 %13
%12 = OpLabel
OpStore %1 %14
OpBranch %13
%7 = OpTypeVoid
%6 = OpTypeFunction %7
%15 = OpConstant %3 1
%16 = OpConstant %3 2
%17 = OpConstant %3 3
%8 = OpFunction %7 None %6
%9 = OpLabel
%11 = OpLoad %3 %5
OpSelectionMerge %10 None
OpSwitch %11 %12 1 %13 2 %14
%13 = OpLabel
OpStore %1 %15
OpBranch %9
%11 = OpLabel
OpBranch %14
%14 = OpLabel
OpStore %1 %16
OpBranch %9
%9 = OpLabel
OpBranch %10
%12 = OpLabel
OpStore %1 %17
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)");
@ -439,8 +444,8 @@ TEST_F(BuilderTest, Switch_CaseFallthroughLastStatement) {
WrapInFunction(expr);
auto* func =
Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
ast::DecorationList{});
spirv::Builder& b = Build();
@ -482,8 +487,8 @@ TEST_F(BuilderTest, Switch_WithNestedBreak) {
WrapInFunction(expr);
auto* func =
Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
ast::DecorationList{});
spirv::Builder& b = Build();
@ -495,32 +500,33 @@ TEST_F(BuilderTest, Switch_WithNestedBreak) {
EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
OpName %5 "a"
OpName %7 "a_func"
OpName %8 "a_func"
%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%4 = OpConstantNull %3
%1 = OpVariable %2 Private %4
%5 = OpVariable %2 Private %4
%6 = OpTypeFunction %3
%13 = OpTypeBool
%14 = OpConstantTrue %13
%17 = OpConstant %3 1
%7 = OpFunction %3 None %6
%8 = OpLabel
%10 = OpLoad %3 %5
OpSelectionMerge %9 None
OpSwitch %10 %11 1 %12
%12 = OpLabel
OpSelectionMerge %15 None
OpBranchConditional %14 %16 %15
%16 = OpLabel
OpBranch %9
%15 = OpLabel
OpStore %1 %17
OpBranch %9
%11 = OpLabel
OpBranch %9
%7 = OpTypeVoid
%6 = OpTypeFunction %7
%14 = OpTypeBool
%15 = OpConstantTrue %14
%18 = OpConstant %3 1
%8 = OpFunction %7 None %6
%9 = OpLabel
%11 = OpLoad %3 %5
OpSelectionMerge %10 None
OpSwitch %11 %12 1 %13
%13 = OpLabel
OpSelectionMerge %16 None
OpBranchConditional %15 %17 %16
%17 = OpLabel
OpBranch %10
%16 = OpLabel
OpStore %1 %18
OpBranch %10
%12 = OpLabel
OpBranch %10
%10 = OpLabel
OpReturn
OpFunctionEnd
)");

View File

@ -171,6 +171,7 @@ source_set("tint_unittests_core_src") {
"../src/program_test.cc",
"../src/resolver/assignment_validation_test.cc",
"../src/resolver/decoration_validation_test.cc",
"../src/resolver/function_validation_test.cc",
"../src/resolver/host_shareable_validation_test.cc",
"../src/resolver/intrinsic_test.cc",
"../src/resolver/is_host_shareable_test.cc",