blob: 549b814713236cc22493997ece05caffad9b12fb [file] [log] [blame]
// 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/override_decoration.h"
#include "src/ast/return_statement.h"
#include "src/ast/stage_decoration.h"
#include "src/ast/struct_block_decoration.h"
#include "src/resolver/resolver.h"
#include "src/resolver/resolver_test_helper.h"
#include "src/sem/multisampled_texture_type.h"
#include "src/sem/storage_texture_type.h"
#include "gmock/gmock.h"
namespace tint {
namespace resolver {
namespace {
// Helpers and typedefs
template <typename T>
using DataType = builder::DataType<T>;
template <typename T>
using vec2 = builder::vec2<T>;
template <typename T>
using vec3 = builder::vec3<T>;
template <typename T>
using vec4 = builder::vec4<T>;
template <typename T>
using mat2x2 = builder::mat2x2<T>;
template <typename T>
using mat3x3 = builder::mat3x3<T>;
template <typename T>
using mat4x4 = builder::mat4x4<T>;
template <int N, typename T>
using array = builder::array<N, T>;
template <typename T>
using alias = builder::alias<T>;
template <typename T>
using alias1 = builder::alias1<T>;
template <typename T>
using alias2 = builder::alias2<T>;
template <typename T>
using alias3 = builder::alias3<T>;
using f32 = builder::f32;
using i32 = builder::i32;
using u32 = builder::u32;
class ResolverTypeValidationTest : public resolver::TestHelper,
public testing::Test {};
TEST_F(ResolverTypeValidationTest, VariableDeclNoConstructor_Pass) {
// {
// var a :i32;
// a = 2;
// }
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, nullptr);
auto* lhs = Expr("a");
auto* rhs = Expr(2);
auto* body =
Block(Decl(var), Assign(Source{Source::Location{12, 34}}, lhs, rhs));
WrapInFunction(body);
EXPECT_TRUE(r()->Resolve()) << r()->error();
ASSERT_NE(TypeOf(lhs), nullptr);
ASSERT_NE(TypeOf(rhs), nullptr);
}
TEST_F(ResolverTypeValidationTest, GlobalConstantNoConstructor_Pass) {
// [[override(0)]] let a :i32;
GlobalConst(Source{{12, 34}}, "a", ty.i32(), nullptr,
ast::DecorationList{create<ast::OverrideDecoration>(0)});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, GlobalVariableWithStorageClass_Pass) {
// var<private> global_var: f32;
Global(Source{{12, 34}}, "global_var", ty.f32(), ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, GlobalConstantWithStorageClass_Fail) {
// const<private> global_var: f32;
AST().AddGlobalVariable(create<ast::Variable>(
Source{{12, 34}}, Symbols().Register("global_var"),
ast::StorageClass::kPrivate, ast::Access::kUndefined, ty.f32(), true,
Expr(1.23f), ast::DecorationList{}));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: global constants shouldn't have a storage class");
}
TEST_F(ResolverTypeValidationTest, GlobalConstNoStorageClass_Pass) {
// let global_var: f32;
GlobalConst(Source{{12, 34}}, "global_var", ty.f32(), Construct(ty.f32()));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, GlobalVariableUnique_Pass) {
// var global_var0 : f32 = 0.1;
// var global_var1 : i32 = 0;
Global("global_var0", ty.f32(), ast::StorageClass::kPrivate, Expr(0.1f));
Global(Source{{12, 34}}, "global_var1", ty.f32(), ast::StorageClass::kPrivate,
Expr(1.0f));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest,
GlobalVariableFunctionVariableNotUnique_Pass) {
// fn my_func() {
// var a: f32 = 2.0;
// }
// var a: f32 = 2.1;
auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
Func("my_func", ast::VariableList{}, ty.void_(), {Decl(var)});
Global("a", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, RedeclaredIdentifierInnerScope_Pass) {
// {
// if (true) { var a : f32 = 2.0; }
// var a : f32 = 3.14;
// }
auto* var = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
auto* cond = Expr(true);
auto* body = Block(Decl(var));
auto* var_a_float = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(3.1f));
auto* outer_body =
Block(create<ast::IfStatement>(cond, body, ast::ElseStatementList{}),
Decl(Source{{12, 34}}, var_a_float));
WrapInFunction(outer_body);
EXPECT_TRUE(r()->Resolve());
}
TEST_F(ResolverTypeValidationTest, RedeclaredIdentifierInnerScopeBlock_Pass) {
// {
// { var a : f32; }
// var a : f32;
// }
auto* var_inner = Var("a", ty.f32(), ast::StorageClass::kNone);
auto* inner = Block(Decl(Source{{12, 34}}, var_inner));
auto* var_outer = Var("a", ty.f32(), ast::StorageClass::kNone);
auto* outer_body = Block(inner, Decl(var_outer));
WrapInFunction(outer_body);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest,
RedeclaredIdentifierDifferentFunctions_Pass) {
// func0 { var a : f32 = 2.0; return; }
// func1 { var a : f32 = 3.0; return; }
auto* var0 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(2.0f));
auto* var1 = Var("a", ty.f32(), ast::StorageClass::kNone, Expr(1.0f));
Func("func0", ast::VariableList{}, ty.void_(),
ast::StatementList{
Decl(Source{{12, 34}}, var0),
Return(),
},
ast::DecorationList{});
Func("func1", ast::VariableList{}, ty.void_(),
ast::StatementList{
Decl(Source{{13, 34}}, var1),
Return(),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Pass) {
// var<private> a : array<f32, 4u>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4u)),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Pass) {
// var<private> a : array<f32, 4>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4)),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Pass) {
// let size = 4u;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(4u));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Pass) {
// let size = 4;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(4));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Zero) {
// var<private> a : array<f32, 0u>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0u)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Zero) {
// var<private> a : array<f32, 0>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedLiteral_Negative) {
// var<private> a : array<f32, -10>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, -10)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Zero) {
// let size = 0u;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(0u));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Zero) {
// let size = 0;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(0));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Negative) {
// let size = -10;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(-10));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
}
TEST_F(ResolverTypeValidationTest, ArraySize_FloatLiteral) {
// var<private> a : array<f32, 10.0>;
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 10.f)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_IVecLiteral) {
// var<private> a : array<f32, vec2<i32>(10, 10)>;
Global(
"a",
ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.vec2<i32>(), 10, 10)),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_FloatConstant) {
// let size = 10.0;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(10.f));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_IVecConstant) {
// let size = vec2<i32>(100, 100);
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Construct(ty.vec2<i32>(), 100, 100));
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
}
TEST_F(ResolverTypeValidationTest, ArraySize_OverridableConstant) {
// [[override]] let size = 10;
// var<private> a : array<f32, size>;
GlobalConst("size", nullptr, Expr(10), {Override()});
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: array size expression must not be pipeline-overridable");
}
TEST_F(ResolverTypeValidationTest, ArraySize_ModuleVar) {
// var<private> size : i32 = 10;
// var<private> a : array<f32, size>;
Global("size", ty.i32(), Expr(10), ast::StorageClass::kPrivate);
Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: array size identifier must be a module-scope constant");
}
TEST_F(ResolverTypeValidationTest, ArraySize_FunctionConstant) {
// {
// let size = 10;
// var a : array<f32, size>;
// }
auto* size = Const("size", nullptr, Expr(10));
auto* a = Var("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, "size")));
WrapInFunction(Block(Decl(size), Decl(a)));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: array size identifier must be a module-scope constant");
}
TEST_F(ResolverTypeValidationTest, ArraySize_InvalidExpr) {
// var a : array<f32, i32(4)>;
auto* size = Const("size", nullptr, Expr(10));
auto* a =
Var("a", ty.array(ty.f32(), Construct(Source{{12, 34}}, ty.i32(), 4)));
WrapInFunction(Block(Decl(size), Decl(a)));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: array size expression must be either a literal or a "
"module-scope constant");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayInFunction_Fail) {
/// [[stage(vertex)]]
// fn func() { var a : array<i32>; }
auto* var =
Var(Source{{12, 34}}, "a", ty.array<i32>(), ast::StorageClass::kNone);
Func("func", ast::VariableList{}, ty.void_(),
ast::StatementList{
Decl(var),
},
ast::DecorationList{
Stage(ast::PipelineStage::kVertex),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: runtime arrays may only appear as the last member of "
"a struct");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLast_Pass) {
// [[Block]]
// struct Foo {
// vf: f32;
// rt: array<f32>;
// };
Structure("Foo",
{
Member("vf", ty.f32()),
Member("rt", ty.array<f32>()),
},
{create<ast::StructBlockDecoration>()});
WrapInFunction();
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayIsLastNoBlock_Fail) {
// struct Foo {
// vf: f32;
// rt: array<f32>;
// };
Structure("Foo", {
Member("vf", ty.f32()),
Member(Source{{12, 34}}, "rt", ty.array<f32>()),
});
WrapInFunction();
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error: a struct containing a runtime-sized array requires "
"the [[block]] attribute: 'Foo'");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayIsNotLast_Fail) {
// [[Block]]
// struct Foo {
// rt: array<f32>;
// vf: f32;
// };
Structure("Foo",
{
Member(Source{{12, 34}}, "rt", ty.array<f32>()),
Member("vf", ty.f32()),
},
{create<ast::StructBlockDecoration>()});
WrapInFunction();
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(
r()->error(),
R"(12:34 error: runtime arrays may only appear as the last member of a struct)");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayAsGlobalVariable) {
Global(Source{{56, 78}}, "g", ty.array<i32>(), ast::StorageClass::kPrivate);
ASSERT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(56:78 error: runtime arrays may only appear as the last member of a struct)");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayAsLocalVariable) {
auto* v = Var(Source{{56, 78}}, "g", ty.array<i32>());
WrapInFunction(v);
ASSERT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(56:78 error: runtime arrays may only appear as the last member of a struct)");
}
TEST_F(ResolverTypeValidationTest, RuntimeArrayAsParameter_Fail) {
// fn func(a : array<u32>) {}
// [[stage(vertex)]] fn main() {}
auto* param = Param(Source{{12, 34}}, "a", ty.array<i32>());
Func("func", ast::VariableList{param}, ty.void_(),
ast::StatementList{
Return(),
},
ast::DecorationList{});
Func("main", ast::VariableList{}, ty.void_(),
ast::StatementList{
Return(),
},
ast::DecorationList{
Stage(ast::PipelineStage::kVertex),
});
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error: runtime arrays may only appear as the last member of "
"a struct");
}
TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
// [[Block]]
// type RTArr = array<u32>;
// struct s {
// b: RTArr;
// a: u32;
//}
auto* alias = Alias("RTArr", ty.array<u32>());
Structure("s",
{
Member(Source{{12, 34}}, "b", ty.Of(alias)),
Member("a", ty.u32()),
},
{create<ast::StructBlockDecoration>()});
WrapInFunction();
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
"12:34 error: runtime arrays may only appear as the last member of "
"a struct");
}
TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsLast_Pass) {
// [[Block]]
// type RTArr = array<u32>;
// struct s {
// a: u32;
// b: RTArr;
//}
auto* alias = Alias("RTArr", ty.array<u32>());
Structure("s",
{
Member("a", ty.u32()),
Member("b", ty.Of(alias)),
},
{create<ast::StructBlockDecoration>()});
WrapInFunction();
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverTypeValidationTest, ArrayOfNonStorableType) {
auto* tex_ty = ty.sampled_texture(ast::TextureDimension::k2d, ty.f32());
Global("arr", ty.array(Source{{12, 34}}, tex_ty, 4),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: texture_2d<f32> cannot be used as an element type of "
"an array");
}
namespace GetCanonicalTests {
struct Params {
builder::ast_type_func_ptr create_ast_type;
builder::sem_type_func_ptr create_sem_type;
};
template <typename T>
constexpr Params ParamsFor() {
return Params{DataType<T>::AST, DataType<T>::Sem};
}
static constexpr Params cases[] = {
ParamsFor<bool>(),
ParamsFor<alias<bool>>(),
ParamsFor<alias1<alias<bool>>>(),
ParamsFor<vec3<f32>>(),
ParamsFor<alias<vec3<f32>>>(),
ParamsFor<alias1<alias<vec3<f32>>>>(),
ParamsFor<vec3<alias<f32>>>(),
ParamsFor<alias1<vec3<alias<f32>>>>(),
ParamsFor<alias2<alias1<vec3<alias<f32>>>>>(),
ParamsFor<alias3<alias2<vec3<alias1<alias<f32>>>>>>(),
ParamsFor<mat3x3<alias<f32>>>(),
ParamsFor<alias1<mat3x3<alias<f32>>>>(),
ParamsFor<alias2<alias1<mat3x3<alias<f32>>>>>(),
ParamsFor<alias3<alias2<mat3x3<alias1<alias<f32>>>>>>(),
ParamsFor<alias1<alias<bool>>>(),
ParamsFor<alias1<alias<vec3<f32>>>>(),
ParamsFor<alias1<alias<mat3x3<f32>>>>(),
};
using CanonicalTest = ResolverTestWithParam<Params>;
TEST_P(CanonicalTest, All) {
auto& params = GetParam();
auto* type = params.create_ast_type(*this);
auto* var = Var("v", type);
auto* expr = Expr("v");
WrapInFunction(var, expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* got = TypeOf(expr)->UnwrapRef();
auto* expected = params.create_sem_type(*this);
EXPECT_EQ(got, expected) << "got: " << FriendlyName(got) << "\n"
<< "expected: " << FriendlyName(expected) << "\n";
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
CanonicalTest,
testing::ValuesIn(cases));
} // namespace GetCanonicalTests
namespace MultisampledTextureTests {
struct DimensionParams {
ast::TextureDimension dim;
bool is_valid;
};
static constexpr DimensionParams dimension_cases[] = {
DimensionParams{ast::TextureDimension::k1d, false},
DimensionParams{ast::TextureDimension::k2d, true},
DimensionParams{ast::TextureDimension::k2dArray, false},
DimensionParams{ast::TextureDimension::k3d, false},
DimensionParams{ast::TextureDimension::kCube, false},
DimensionParams{ast::TextureDimension::kCubeArray, false}};
using MultisampledTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
TEST_P(MultisampledTextureDimensionTest, All) {
auto& params = GetParam();
Global(Source{{12, 34}}, "a", ty.multisampled_texture(params.dim, ty.i32()),
ast::StorageClass::kNone, nullptr,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
if (params.is_valid) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: only 2d multisampled textures are supported");
}
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
MultisampledTextureDimensionTest,
testing::ValuesIn(dimension_cases));
struct TypeParams {
builder::ast_type_func_ptr type_func;
bool is_valid;
};
template <typename T>
constexpr TypeParams TypeParamsFor(bool is_valid) {
return TypeParams{DataType<T>::AST, is_valid};
}
static constexpr TypeParams type_cases[] = {
TypeParamsFor<bool>(false),
TypeParamsFor<i32>(true),
TypeParamsFor<u32>(true),
TypeParamsFor<f32>(true),
TypeParamsFor<alias<bool>>(false),
TypeParamsFor<alias<i32>>(true),
TypeParamsFor<alias<u32>>(true),
TypeParamsFor<alias<f32>>(true),
TypeParamsFor<vec3<f32>>(false),
TypeParamsFor<mat3x3<f32>>(false),
TypeParamsFor<alias<vec3<f32>>>(false),
TypeParamsFor<alias<mat3x3<f32>>>(false),
};
using MultisampledTextureTypeTest = ResolverTestWithParam<TypeParams>;
TEST_P(MultisampledTextureTypeTest, All) {
auto& params = GetParam();
Global(Source{{12, 34}}, "a",
ty.multisampled_texture(ast::TextureDimension::k2d,
params.type_func(*this)),
ast::StorageClass::kNone, nullptr,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
if (params.is_valid) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: texture_multisampled_2d<type>: type must be f32, "
"i32 or u32");
}
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
MultisampledTextureTypeTest,
testing::ValuesIn(type_cases));
} // namespace MultisampledTextureTests
namespace StorageTextureTests {
struct DimensionParams {
ast::TextureDimension dim;
bool is_valid;
};
static constexpr DimensionParams Dimension_cases[] = {
DimensionParams{ast::TextureDimension::k1d, true},
DimensionParams{ast::TextureDimension::k2d, true},
DimensionParams{ast::TextureDimension::k2dArray, true},
DimensionParams{ast::TextureDimension::k3d, true},
DimensionParams{ast::TextureDimension::kCube, false},
DimensionParams{ast::TextureDimension::kCubeArray, false}};
using StorageTextureDimensionTest = ResolverTestWithParam<DimensionParams>;
TEST_P(StorageTextureDimensionTest, All) {
// [[group(0), binding(0)]]
// var a : texture_storage_*<ru32int, read>;
auto& params = GetParam();
auto* st = ty.storage_texture(Source{{12, 34}}, params.dim,
ast::ImageFormat::kR32Uint, ast::Access::kRead);
Global("a", st, ast::StorageClass::kNone,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
if (params.is_valid) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: cube dimensions for storage textures are not supported");
}
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
StorageTextureDimensionTest,
testing::ValuesIn(Dimension_cases));
struct FormatParams {
ast::ImageFormat format;
bool is_valid;
};
static constexpr FormatParams format_cases[] = {
FormatParams{ast::ImageFormat::kBgra8Unorm, false},
FormatParams{ast::ImageFormat::kBgra8UnormSrgb, false},
FormatParams{ast::ImageFormat::kR16Float, false},
FormatParams{ast::ImageFormat::kR16Sint, false},
FormatParams{ast::ImageFormat::kR16Uint, false},
FormatParams{ast::ImageFormat::kR32Float, true},
FormatParams{ast::ImageFormat::kR32Sint, true},
FormatParams{ast::ImageFormat::kR32Uint, true},
FormatParams{ast::ImageFormat::kR8Sint, false},
FormatParams{ast::ImageFormat::kR8Snorm, false},
FormatParams{ast::ImageFormat::kR8Uint, false},
FormatParams{ast::ImageFormat::kR8Unorm, false},
FormatParams{ast::ImageFormat::kRg11B10Float, false},
FormatParams{ast::ImageFormat::kRg16Float, false},
FormatParams{ast::ImageFormat::kRg16Sint, false},
FormatParams{ast::ImageFormat::kRg16Uint, false},
FormatParams{ast::ImageFormat::kRg32Float, true},
FormatParams{ast::ImageFormat::kRg32Sint, true},
FormatParams{ast::ImageFormat::kRg32Uint, true},
FormatParams{ast::ImageFormat::kRg8Sint, false},
FormatParams{ast::ImageFormat::kRg8Snorm, false},
FormatParams{ast::ImageFormat::kRg8Uint, false},
FormatParams{ast::ImageFormat::kRg8Unorm, false},
FormatParams{ast::ImageFormat::kRgb10A2Unorm, false},
FormatParams{ast::ImageFormat::kRgba16Float, true},
FormatParams{ast::ImageFormat::kRgba16Sint, true},
FormatParams{ast::ImageFormat::kRgba16Uint, true},
FormatParams{ast::ImageFormat::kRgba32Float, true},
FormatParams{ast::ImageFormat::kRgba32Sint, true},
FormatParams{ast::ImageFormat::kRgba32Uint, true},
FormatParams{ast::ImageFormat::kRgba8Sint, true},
FormatParams{ast::ImageFormat::kRgba8Snorm, true},
FormatParams{ast::ImageFormat::kRgba8Uint, true},
FormatParams{ast::ImageFormat::kRgba8Unorm, true},
FormatParams{ast::ImageFormat::kRgba8UnormSrgb, false}};
using StorageTextureFormatTest = ResolverTestWithParam<FormatParams>;
TEST_P(StorageTextureFormatTest, All) {
auto& params = GetParam();
// [[group(0), binding(0)]]
// var a : texture_storage_1d<*, read>;
// [[group(0), binding(1)]]
// var b : texture_storage_2d<*, read>;
// [[group(0), binding(2)]]
// var c : texture_storage_2d_array<*, read>;
// [[group(0), binding(3)]]
// var d : texture_storage_3d<*, read>;
auto* st_a = ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
params.format, ast::Access::kRead);
Global("a", st_a, ast::StorageClass::kNone,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
auto* st_b = ty.storage_texture(ast::TextureDimension::k2d, params.format,
ast::Access::kRead);
Global("b", st_b, ast::StorageClass::kNone,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(1),
});
auto* st_c = ty.storage_texture(ast::TextureDimension::k2dArray,
params.format, ast::Access::kRead);
Global("c", st_c, ast::StorageClass::kNone,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(2),
});
auto* st_d = ty.storage_texture(ast::TextureDimension::k3d, params.format,
ast::Access::kRead);
Global("d", st_d, ast::StorageClass::kNone,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(3),
});
if (params.is_valid) {
EXPECT_TRUE(r()->Resolve()) << r()->error();
} else {
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: image format must be one of the texel formats "
"specified for storage textues in "
"https://gpuweb.github.io/gpuweb/wgsl/#texel-formats");
}
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
StorageTextureFormatTest,
testing::ValuesIn(format_cases));
using StorageTextureAccessTest = ResolverTest;
TEST_F(StorageTextureAccessTest, MissingAccess_Fail) {
// [[group(0), binding(0)]]
// var a : texture_storage_1d<ru32int>;
auto* st =
ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
ast::ImageFormat::kR32Uint, ast::Access::kUndefined);
Global("a", st, ast::StorageClass::kNone,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: storage textures must have access control");
}
TEST_F(StorageTextureAccessTest, RWAccess_Fail) {
// [[group(0), binding(0)]]
// var a : texture_storage_1d<ru32int, read_write>;
auto* st =
ty.storage_texture(Source{{12, 34}}, ast::TextureDimension::k1d,
ast::ImageFormat::kR32Uint, ast::Access::kReadWrite);
Global("a", st, ast::StorageClass::kNone, nullptr,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
"12:34 error: storage textures only support read-only and "
"write-only access");
}
TEST_F(StorageTextureAccessTest, ReadOnlyAccess_Pass) {
// [[group(0), binding(0)]]
// var a : texture_storage_1d<ru32int, read>;
auto* st = ty.storage_texture(ast::TextureDimension::k1d,
ast::ImageFormat::kR32Uint, ast::Access::kRead);
Global("a", st, ast::StorageClass::kNone, nullptr,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(StorageTextureAccessTest, WriteOnlyAccess_Pass) {
// [[group(0), binding(0)]]
// var a : texture_storage_1d<ru32int, write>;
auto* st =
ty.storage_texture(ast::TextureDimension::k1d, ast::ImageFormat::kR32Uint,
ast::Access::kWrite);
Global("a", st, ast::StorageClass::kNone, nullptr,
ast::DecorationList{
create<ast::BindingDecoration>(0),
create<ast::GroupDecoration>(0),
});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
} // namespace StorageTextureTests
namespace MatrixTests {
struct Params {
uint32_t columns;
uint32_t rows;
builder::ast_type_func_ptr elem_ty;
};
template <typename T>
constexpr Params ParamsFor(uint32_t columns, uint32_t rows) {
return Params{columns, rows, DataType<T>::AST};
}
using ValidMatrixTypes = ResolverTestWithParam<Params>;
TEST_P(ValidMatrixTypes, Okay) {
// var a : matNxM<EL_TY>;
auto& params = GetParam();
Global("a", ty.mat(params.elem_ty(*this), params.columns, params.rows),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
ValidMatrixTypes,
testing::Values(ParamsFor<f32>(2, 2),
ParamsFor<f32>(2, 3),
ParamsFor<f32>(2, 4),
ParamsFor<f32>(3, 2),
ParamsFor<f32>(3, 3),
ParamsFor<f32>(3, 4),
ParamsFor<f32>(4, 2),
ParamsFor<f32>(4, 3),
ParamsFor<f32>(4, 4),
ParamsFor<alias<f32>>(4, 2),
ParamsFor<alias<f32>>(4, 3),
ParamsFor<alias<f32>>(4, 4)));
using InvalidMatrixElementTypes = ResolverTestWithParam<Params>;
TEST_P(InvalidMatrixElementTypes, InvalidElementType) {
// var a : matNxM<EL_TY>;
auto& params = GetParam();
Global("a",
ty.mat(Source{{12, 34}}, params.elem_ty(*this), params.columns,
params.rows),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), "12:34 error: matrix element type must be 'f32'");
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
InvalidMatrixElementTypes,
testing::Values(ParamsFor<bool>(4, 2),
ParamsFor<i32>(4, 3),
ParamsFor<u32>(4, 4),
ParamsFor<vec2<f32>>(2, 2),
ParamsFor<vec3<i32>>(2, 3),
ParamsFor<vec4<u32>>(2, 4),
ParamsFor<mat2x2<f32>>(3, 2),
ParamsFor<mat3x3<f32>>(3, 3),
ParamsFor<mat4x4<f32>>(3, 4),
ParamsFor<array<2, f32>>(4, 2)));
} // namespace MatrixTests
namespace VectorTests {
struct Params {
uint32_t width;
builder::ast_type_func_ptr elem_ty;
};
template <typename T>
constexpr Params ParamsFor(uint32_t width) {
return Params{width, DataType<T>::AST};
}
using ValidVectorTypes = ResolverTestWithParam<Params>;
TEST_P(ValidVectorTypes, Okay) {
// var a : vecN<EL_TY>;
auto& params = GetParam();
Global("a", ty.vec(params.elem_ty(*this), params.width),
ast::StorageClass::kPrivate);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
ValidVectorTypes,
testing::Values(ParamsFor<bool>(2),
ParamsFor<f32>(2),
ParamsFor<i32>(2),
ParamsFor<u32>(2),
ParamsFor<bool>(3),
ParamsFor<f32>(3),
ParamsFor<i32>(3),
ParamsFor<u32>(3),
ParamsFor<bool>(4),
ParamsFor<f32>(4),
ParamsFor<i32>(4),
ParamsFor<u32>(4),
ParamsFor<alias<bool>>(4),
ParamsFor<alias<f32>>(4),
ParamsFor<alias<i32>>(4),
ParamsFor<alias<u32>>(4)));
using InvalidVectorElementTypes = ResolverTestWithParam<Params>;
TEST_P(InvalidVectorElementTypes, InvalidElementType) {
// var a : vecN<EL_TY>;
auto& params = GetParam();
Global("a", ty.vec(Source{{12, 34}}, params.elem_ty(*this), params.width),
ast::StorageClass::kPrivate);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
"12:34 error: vector element type must be 'bool', 'f32', 'i32' or 'u32'");
}
INSTANTIATE_TEST_SUITE_P(ResolverTypeValidationTest,
InvalidVectorElementTypes,
testing::Values(ParamsFor<vec2<f32>>(2),
ParamsFor<vec3<i32>>(2),
ParamsFor<vec4<u32>>(2),
ParamsFor<mat2x2<f32>>(2),
ParamsFor<mat3x3<f32>>(2),
ParamsFor<mat4x4<f32>>(2),
ParamsFor<array<2, f32>>(2)));
} // namespace VectorTests
} // namespace
} // namespace resolver
} // namespace tint