mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-21 18:59:21 +00:00
tint->dawn: Shuffle source tree in preperation of merging repos
docs/ -> docs/tint/ fuzzers/ -> src/tint/fuzzers/ samples/ -> src/tint/cmd/ src/ -> src/tint/ test/ -> test/tint/ BUG=tint:1418,tint:1433 Change-Id: Id2aa79f989aef3245b80ef4aa37a27ff16cd700b Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/80482 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Ben Clayton <bclayton@google.com> Commit-Queue: Ryan Harrison <rharrison@chromium.org>
This commit is contained in:
committed by
Tint LUCI CQ
parent
38f1e9c75c
commit
dbc13af287
312
src/tint/resolver/array_accessor_test.cc
Normal file
312
src/tint/resolver/array_accessor_test.cc
Normal file
@@ -0,0 +1,312 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/reference_type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverIndexAccessorTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic_F32) {
|
||||
Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
|
||||
auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 1.0f));
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic_Ref) {
|
||||
Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
|
||||
auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
|
||||
auto* acc = IndexAccessor("my_var", idx);
|
||||
WrapInFunction(Decl(idx), acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix_BothDimensions_Dynamic_Ref) {
|
||||
Global("my_var", ty.mat4x4<f32>(), ast::StorageClass::kPrivate);
|
||||
auto* idx = Var("idx", ty.u32(), Expr(3u));
|
||||
auto* idy = Var("idy", ty.u32(), Expr(2u));
|
||||
auto* acc = IndexAccessor(IndexAccessor("my_var", idx), idy);
|
||||
WrapInFunction(Decl(idx), Decl(idy), acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix_Dynamic) {
|
||||
GlobalConst("my_const", ty.mat2x3<f32>(), Construct(ty.mat2x3<f32>()));
|
||||
auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
|
||||
auto* acc = IndexAccessor("my_const", Expr(Source{{12, 34}}, idx));
|
||||
WrapInFunction(Decl(idx), acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix_XDimension_Dynamic) {
|
||||
GlobalConst("my_var", ty.mat4x4<f32>(), Construct(ty.mat4x4<f32>()));
|
||||
auto* idx = Var("idx", ty.u32(), Expr(3u));
|
||||
auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, idx));
|
||||
WrapInFunction(Decl(idx), acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix_BothDimension_Dynamic) {
|
||||
GlobalConst("my_var", ty.mat4x4<f32>(), Construct(ty.mat4x4<f32>()));
|
||||
auto* idx = Var("idy", ty.u32(), Expr(2u));
|
||||
auto* acc =
|
||||
IndexAccessor(IndexAccessor("my_var", Expr(Source{{12, 34}}, idx)), 1);
|
||||
WrapInFunction(Decl(idx), acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix) {
|
||||
Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
|
||||
|
||||
auto* acc = IndexAccessor("my_var", 2);
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(acc), nullptr);
|
||||
ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
|
||||
|
||||
auto* ref = TypeOf(acc)->As<sem::Reference>();
|
||||
ASSERT_TRUE(ref->StoreType()->Is<sem::Vector>());
|
||||
EXPECT_EQ(ref->StoreType()->As<sem::Vector>()->Width(), 3u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Matrix_BothDimensions) {
|
||||
Global("my_var", ty.mat2x3<f32>(), ast::StorageClass::kPrivate);
|
||||
|
||||
auto* acc = IndexAccessor(IndexAccessor("my_var", 2), 1);
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(acc), nullptr);
|
||||
ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
|
||||
|
||||
auto* ref = TypeOf(acc)->As<sem::Reference>();
|
||||
EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Vector_F32) {
|
||||
Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
|
||||
auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, 2.0f));
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Vector_Dynamic_Ref) {
|
||||
Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
|
||||
auto* idx = Var("idx", ty.i32(), Expr(2));
|
||||
auto* acc = IndexAccessor("my_var", idx);
|
||||
WrapInFunction(Decl(idx), acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Vector_Dynamic) {
|
||||
GlobalConst("my_var", ty.vec3<f32>(), Construct(ty.vec3<f32>()));
|
||||
auto* idx = Var("idx", ty.i32(), Expr(2));
|
||||
auto* acc = IndexAccessor("my_var", Expr(Source{{12, 34}}, idx));
|
||||
WrapInFunction(Decl(idx), acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Vector) {
|
||||
Global("my_var", ty.vec3<f32>(), ast::StorageClass::kPrivate);
|
||||
|
||||
auto* acc = IndexAccessor("my_var", 2);
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(acc), nullptr);
|
||||
ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
|
||||
|
||||
auto* ref = TypeOf(acc)->As<sem::Reference>();
|
||||
EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Array) {
|
||||
auto* idx = Expr(2);
|
||||
Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kPrivate);
|
||||
|
||||
auto* acc = IndexAccessor("my_var", idx);
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(acc), nullptr);
|
||||
ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
|
||||
|
||||
auto* ref = TypeOf(acc)->As<sem::Reference>();
|
||||
EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Alias_Array) {
|
||||
auto* aary = Alias("myarrty", ty.array<f32, 3>());
|
||||
|
||||
Global("my_var", ty.Of(aary), ast::StorageClass::kPrivate);
|
||||
|
||||
auto* acc = IndexAccessor("my_var", 2);
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(acc), nullptr);
|
||||
ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
|
||||
|
||||
auto* ref = TypeOf(acc)->As<sem::Reference>();
|
||||
EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Array_Constant) {
|
||||
GlobalConst("my_var", ty.array<f32, 3>(), array<f32, 3>());
|
||||
|
||||
auto* acc = IndexAccessor("my_var", 2);
|
||||
WrapInFunction(acc);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(acc), nullptr);
|
||||
EXPECT_TRUE(TypeOf(acc)->Is<sem::F32>()) << TypeOf(acc)->type_name();
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Array_Dynamic_I32) {
|
||||
// let a : array<f32, 3> = 0;
|
||||
// var idx : i32 = 0;
|
||||
// var f : f32 = a[idx];
|
||||
auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
|
||||
auto* idx = Var("idx", ty.i32(), Construct(ty.i32()));
|
||||
auto* f = Var("f", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, idx)));
|
||||
Func("my_func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(a),
|
||||
Decl(idx),
|
||||
Decl(f),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Array_Literal_F32) {
|
||||
// let a : array<f32, 3>;
|
||||
// var f : f32 = a[2.0f];
|
||||
auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
|
||||
auto* f =
|
||||
Var("a_2", ty.f32(), IndexAccessor("a", Expr(Source{{12, 34}}, 2.0f)));
|
||||
Func("my_func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(a),
|
||||
Decl(f),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: index must be of type 'i32' or 'u32', found: 'f32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Array_Literal_I32) {
|
||||
// let a : array<f32, 3>;
|
||||
// var f : f32 = a[2];
|
||||
auto* a = Const("a", ty.array<f32, 3>(), array<f32, 3>());
|
||||
auto* f = Var("a_2", ty.f32(), IndexAccessor("a", 2));
|
||||
Func("my_func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(a),
|
||||
Decl(f),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncGoodParent) {
|
||||
// fn func(p: ptr<function, vec4<f32>>) -> f32 {
|
||||
// let idx: u32 = u32();
|
||||
// let x: f32 = (*p)[idx];
|
||||
// return x;
|
||||
// }
|
||||
auto* p =
|
||||
Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
|
||||
auto* idx = Const("idx", ty.u32(), Construct(ty.u32()));
|
||||
auto* star_p = Deref(p);
|
||||
auto* accessor_expr = IndexAccessor(Source{{12, 34}}, star_p, idx);
|
||||
auto* x = Var("x", ty.f32(), accessor_expr);
|
||||
Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncBadParent) {
|
||||
// fn func(p: ptr<function, vec4<f32>>) -> f32 {
|
||||
// let idx: u32 = u32();
|
||||
// let x: f32 = *p[idx];
|
||||
// return x;
|
||||
// }
|
||||
auto* p =
|
||||
Param("p", ty.pointer(ty.vec4<f32>(), ast::StorageClass::kFunction));
|
||||
auto* idx = Const("idx", ty.u32(), Construct(ty.u32()));
|
||||
auto* accessor_expr = IndexAccessor(Source{{12, 34}}, p, idx);
|
||||
auto* star_p = Deref(accessor_expr);
|
||||
auto* x = Var("x", ty.f32(), star_p);
|
||||
Func("func", {p}, ty.f32(), {Decl(idx), Decl(x), Return(x)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIndexAccessorTest, Exr_Deref_BadParent) {
|
||||
// var param: vec4<f32>
|
||||
// let x: f32 = *(¶m)[0];
|
||||
auto* param = Var("param", ty.vec4<f32>());
|
||||
auto* idx = Var("idx", ty.u32(), Construct(ty.u32()));
|
||||
auto* addressOf_expr = AddressOf(param);
|
||||
auto* accessor_expr = IndexAccessor(Source{{12, 34}}, addressOf_expr, idx);
|
||||
auto* star_p = Deref(accessor_expr);
|
||||
auto* x = Var("x", ty.f32(), star_p);
|
||||
WrapInFunction(param, idx, x);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: cannot index type 'ptr<function, vec4<f32>, read_write>'");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
403
src/tint/resolver/assignment_validation_test.cc
Normal file
403
src/tint/resolver/assignment_validation_test.cc
Normal file
@@ -0,0 +1,403 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/storage_texture_type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverAssignmentValidationTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, ReadOnlyBuffer) {
|
||||
// [[block]] struct S { m : i32 };
|
||||
// @group(0) @binding(0)
|
||||
// var<storage,read> a : S;
|
||||
auto* s = Structure("S", {Member("m", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
Global(Source{{12, 34}}, "a", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("a", "m"), 1));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: cannot store into a read-only type 'ref<storage, "
|
||||
"i32, read>'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignIncompatibleTypes) {
|
||||
// {
|
||||
// var a : i32 = 2;
|
||||
// a = 2.3;
|
||||
// }
|
||||
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
|
||||
auto* assign = Assign(Source{{12, 34}}, "a", 2.3f);
|
||||
WrapInFunction(var, assign);
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignArraysWithDifferentSizeExpressions_Pass) {
|
||||
// let len = 4u;
|
||||
// {
|
||||
// var a : array<f32, 4>;
|
||||
// var b : array<f32, len>;
|
||||
// a = b;
|
||||
// }
|
||||
|
||||
GlobalConst("len", nullptr, Expr(4u));
|
||||
|
||||
auto* a = Var("a", ty.array(ty.f32(), 4));
|
||||
auto* b = Var("b", ty.array(ty.f32(), "len"));
|
||||
|
||||
auto* assign = Assign(Source{{12, 34}}, "a", "b");
|
||||
WrapInFunction(a, b, assign);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignArraysWithDifferentSizeExpressions_Fail) {
|
||||
// let len = 5u;
|
||||
// {
|
||||
// var a : array<f32, 4>;
|
||||
// var b : array<f32, len>;
|
||||
// a = b;
|
||||
// }
|
||||
|
||||
GlobalConst("len", nullptr, Expr(5u));
|
||||
|
||||
auto* a = Var("a", ty.array(ty.f32(), 4));
|
||||
auto* b = Var("b", ty.array(ty.f32(), "len"));
|
||||
|
||||
auto* assign = Assign(Source{{12, 34}}, "a", "b");
|
||||
WrapInFunction(a, b, assign);
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot assign 'array<f32, 5>' to 'array<f32, 4>'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignCompatibleTypesInBlockStatement_Pass) {
|
||||
// {
|
||||
// var a : i32 = 2;
|
||||
// a = 2
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
WrapInFunction(var, Assign("a", 2));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignIncompatibleTypesInBlockStatement_Fail) {
|
||||
// {
|
||||
// var a : i32 = 2;
|
||||
// a = 2.3;
|
||||
// }
|
||||
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2.3f));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignIncompatibleTypesInNestedBlockStatement_Fail) {
|
||||
// {
|
||||
// {
|
||||
// var a : i32 = 2;
|
||||
// a = 2.3;
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* inner_block = Block(Decl(var), Assign(Source{{12, 34}}, "a", 2.3f));
|
||||
auto* outer_block = Block(inner_block);
|
||||
WrapInFunction(outer_block);
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot assign 'f32' to 'i32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignToScalar_Fail) {
|
||||
// var my_var : i32 = 2;
|
||||
// 1 = my_var;
|
||||
|
||||
auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
WrapInFunction(var, Assign(Expr(Source{{12, 34}}, 1), "my_var"));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot assign to value of type 'i32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignCompatibleTypes_Pass) {
|
||||
// var a : i32 = 2;
|
||||
// a = 2
|
||||
auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignCompatibleTypesThroughAlias_Pass) {
|
||||
// alias myint = i32;
|
||||
// var a : myint = 2;
|
||||
// a = 2
|
||||
auto* myint = Alias("myint", ty.i32());
|
||||
auto* var = Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(2));
|
||||
WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignCompatibleTypesInferRHSLoad_Pass) {
|
||||
// var a : i32 = 2;
|
||||
// var b : i32 = 3;
|
||||
// a = b;
|
||||
auto* var_a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
|
||||
auto* var_b = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3));
|
||||
WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, "a", "b"));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignThroughPointer_Pass) {
|
||||
// var a : i32;
|
||||
// let b : ptr<function,i32> = &a;
|
||||
// *b = 2;
|
||||
const auto func = ast::StorageClass::kFunction;
|
||||
auto* var_a = Var("a", ty.i32(), func, Expr(2));
|
||||
auto* var_b = Const("b", ty.pointer<int>(func), AddressOf(Expr("a")));
|
||||
WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, Deref("b"), 2));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignToConstant_Fail) {
|
||||
// {
|
||||
// let a : i32 = 2;
|
||||
// a = 2
|
||||
// }
|
||||
auto* var = Const("a", ty.i32(), Expr(2));
|
||||
WrapInFunction(var, Assign(Expr(Source{{12, 34}}, "a"), 2));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot assign to const\nnote: 'a' is declared here:");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Handle) {
|
||||
// var a : texture_storage_1d<rgba8unorm, write>;
|
||||
// var b : texture_storage_1d<rgba8unorm, write>;
|
||||
// a = b;
|
||||
|
||||
auto make_type = [&] {
|
||||
return ty.storage_texture(ast::TextureDimension::k1d,
|
||||
ast::TexelFormat::kRgba8Unorm,
|
||||
ast::Access::kWrite);
|
||||
};
|
||||
|
||||
Global("a", make_type(), ast::StorageClass::kNone,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
Global("b", make_type(), ast::StorageClass::kNone,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(1),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
WrapInFunction(Assign(Source{{56, 78}}, "a", "b"));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: storage type of assignment must be constructible");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_Atomic) {
|
||||
// [[block]] struct S { a : atomic<i32>; };
|
||||
// @group(0) @binding(0) var<storage, read_write> v : S;
|
||||
// v.a = v.a;
|
||||
|
||||
auto* s = Structure("S", {Member("a", ty.atomic(ty.i32()))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
|
||||
MemberAccessor("v", "a")));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: storage type of assignment must be constructible");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignNonConstructible_RuntimeArray) {
|
||||
// [[block]] struct S { a : array<f32>; };
|
||||
// @group(0) @binding(0) var<storage, read_write> v : S;
|
||||
// v.a = v.a;
|
||||
|
||||
auto* s = Structure("S", {Member("a", ty.array(ty.f32()))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
WrapInFunction(Assign(Source{{56, 78}}, MemberAccessor("v", "a"),
|
||||
MemberAccessor("v", "a")));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: storage type of assignment must be constructible");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest,
|
||||
AssignToPhony_NonConstructibleStruct_Fail) {
|
||||
// [[block]]
|
||||
// struct S {
|
||||
// arr: array<i32>;
|
||||
// };
|
||||
// @group(0) @binding(0) var<storage, read_write> s : S;
|
||||
// fn f() {
|
||||
// _ = s;
|
||||
// }
|
||||
auto* s = Structure("S", {Member("arr", ty.array<i32>())}, {StructBlock()});
|
||||
Global("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
|
||||
|
||||
WrapInFunction(Assign(Phony(), Expr(Source{{12, 34}}, "s")));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot assign 'S' to '_'. "
|
||||
"'_' can only be assigned a constructible, pointer, texture or "
|
||||
"sampler type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignToPhony_DynamicArray_Fail) {
|
||||
// [[block]]
|
||||
// struct S {
|
||||
// arr: array<i32>;
|
||||
// };
|
||||
// @group(0) @binding(0) var<storage, read_write> s : S;
|
||||
// fn f() {
|
||||
// _ = s.arr;
|
||||
// }
|
||||
auto* s = Structure("S", {Member("arr", ty.array<i32>())}, {StructBlock()});
|
||||
Global("s", ty.Of(s), ast::StorageClass::kStorage, GroupAndBinding(0, 0));
|
||||
|
||||
WrapInFunction(Assign(Phony(), MemberAccessor(Source{{12, 34}}, "s", "arr")));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: cannot assign 'array<i32>' to '_'. "
|
||||
"'_' can only be assigned a constructible, pointer, texture or sampler "
|
||||
"type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAssignmentValidationTest, AssignToPhony_Pass) {
|
||||
// [[block]]
|
||||
// struct S {
|
||||
// i: i32;
|
||||
// arr: array<i32>;
|
||||
// };
|
||||
// [[block]]
|
||||
// struct U {
|
||||
// i: i32;
|
||||
// };
|
||||
// @group(0) @binding(0) var tex texture_2d;
|
||||
// @group(0) @binding(1) var smp sampler;
|
||||
// @group(0) @binding(2) var<uniform> u : U;
|
||||
// @group(0) @binding(3) var<storage, read_write> s : S;
|
||||
// var<workgroup> wg : array<f32, 10>
|
||||
// fn f() {
|
||||
// _ = 1;
|
||||
// _ = 2u;
|
||||
// _ = 3.0;
|
||||
// _ = vec2<bool>();
|
||||
// _ = tex;
|
||||
// _ = smp;
|
||||
// _ = &s;
|
||||
// _ = s.i;
|
||||
// _ = &s.arr;
|
||||
// _ = u;
|
||||
// _ = u.i;
|
||||
// _ = wg;
|
||||
// _ = wg[3];
|
||||
// }
|
||||
auto* S = Structure("S",
|
||||
{
|
||||
Member("i", ty.i32()),
|
||||
Member("arr", ty.array<i32>()),
|
||||
},
|
||||
{StructBlock()});
|
||||
auto* U = Structure("U", {Member("i", ty.i32())}, {StructBlock()});
|
||||
Global("tex", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||
GroupAndBinding(0, 0));
|
||||
Global("smp", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 1));
|
||||
Global("u", ty.Of(U), ast::StorageClass::kUniform, GroupAndBinding(0, 2));
|
||||
Global("s", ty.Of(S), ast::StorageClass::kStorage, GroupAndBinding(0, 3));
|
||||
Global("wg", ty.array<f32, 10>(), ast::StorageClass::kWorkgroup);
|
||||
|
||||
WrapInFunction(Assign(Phony(), 1), //
|
||||
Assign(Phony(), 2), //
|
||||
Assign(Phony(), 3), //
|
||||
Assign(Phony(), vec2<bool>()), //
|
||||
Assign(Phony(), "tex"), //
|
||||
Assign(Phony(), "smp"), //
|
||||
Assign(Phony(), AddressOf("s")), //
|
||||
Assign(Phony(), MemberAccessor("s", "i")), //
|
||||
Assign(Phony(), AddressOf(MemberAccessor("s", "arr"))), //
|
||||
Assign(Phony(), "u"), //
|
||||
Assign(Phony(), MemberAccessor("u", "i")), //
|
||||
Assign(Phony(), "wg"), //
|
||||
Assign(Phony(), IndexAccessor("wg", 3)));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
74
src/tint/resolver/atomics_test.cc
Normal file
74
src/tint/resolver/atomics_test.cc
Normal file
@@ -0,0 +1,74 @@
|
||||
// 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/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/atomic_type.h"
|
||||
#include "src/tint/sem/reference_type.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
struct ResolverAtomicTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverAtomicTest, GlobalWorkgroupI32) {
|
||||
auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
|
||||
ast::StorageClass::kWorkgroup);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
|
||||
auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
|
||||
ASSERT_NE(atomic, nullptr);
|
||||
EXPECT_TRUE(atomic->Type()->Is<sem::I32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicTest, GlobalWorkgroupU32) {
|
||||
auto* g = Global("a", ty.atomic(Source{{12, 34}}, ty.u32()),
|
||||
ast::StorageClass::kWorkgroup);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
|
||||
auto* atomic = TypeOf(g)->UnwrapRef()->As<sem::Atomic>();
|
||||
ASSERT_NE(atomic, nullptr);
|
||||
EXPECT_TRUE(atomic->Type()->Is<sem::U32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicTest, GlobalStorageStruct) {
|
||||
auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* g = Global("g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
ASSERT_TRUE(TypeOf(g)->Is<sem::Reference>());
|
||||
auto* str = TypeOf(g)->UnwrapRef()->As<sem::Struct>();
|
||||
ASSERT_NE(str, nullptr);
|
||||
ASSERT_EQ(str->Members().size(), 1u);
|
||||
auto* atomic = str->Members()[0]->Type()->As<sem::Atomic>();
|
||||
ASSERT_NE(atomic, nullptr);
|
||||
ASSERT_TRUE(atomic->Type()->Is<sem::I32>());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
332
src/tint/resolver/atomics_validation_test.cc
Normal file
332
src/tint/resolver/atomics_validation_test.cc
Normal file
@@ -0,0 +1,332 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/atomic_type.h"
|
||||
#include "src/tint/sem/reference_type.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
struct ResolverAtomicValidationTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, StorageClass_WorkGroup) {
|
||||
Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
|
||||
ast::StorageClass::kWorkgroup);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve());
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, StorageClass_Storage) {
|
||||
auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
|
||||
{StructBlock()});
|
||||
Global("g", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kReadWrite,
|
||||
GroupAndBinding(0, 0));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidType) {
|
||||
Global("a", ty.atomic(ty.f32(Source{{12, 34}})),
|
||||
ast::StorageClass::kWorkgroup);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: atomic only supports i32 or u32 types");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Simple) {
|
||||
Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
|
||||
ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Array) {
|
||||
Global("a", ty.atomic(Source{{12, 34}}, ty.i32()),
|
||||
ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Struct) {
|
||||
auto* s =
|
||||
Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
|
||||
Global("g", ty.Of(s), ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class\n"
|
||||
"note: atomic sub-type of 's' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_StructOfStruct) {
|
||||
// struct Inner { m : atomic<i32>; };
|
||||
// struct Outer { m : array<Inner, 4>; };
|
||||
// var<private> g : Outer;
|
||||
|
||||
auto* Inner =
|
||||
Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
|
||||
auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
|
||||
Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class\n"
|
||||
"note: atomic sub-type of 'Outer' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest,
|
||||
InvalidStorageClass_StructOfStructOfArray) {
|
||||
// struct Inner { m : array<atomic<i32>, 4>; };
|
||||
// struct Outer { m : array<Inner, 4>; };
|
||||
// var<private> g : Outer;
|
||||
|
||||
auto* Inner =
|
||||
Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
|
||||
auto* Outer = Structure("Outer", {Member("m", ty.Of(Inner))});
|
||||
Global("g", ty.Of(Outer), ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class\n"
|
||||
"12:34 note: atomic sub-type of 'Outer' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfArray) {
|
||||
// type AtomicArray = array<atomic<i32>, 5>;
|
||||
// var<private> v: array<s, 5>;
|
||||
|
||||
auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
|
||||
ty.atomic(Source{{12, 34}}, ty.i32()));
|
||||
Global(Source{{56, 78}}, "v", ty.Of(atomic_array),
|
||||
ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStruct) {
|
||||
// struct S{
|
||||
// m: atomic<u32>;
|
||||
// };
|
||||
// var<private> v: array<S, 5>;
|
||||
|
||||
auto* s = Structure("S", {Member("m", ty.atomic<u32>())});
|
||||
Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
|
||||
ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class\n"
|
||||
"note: atomic sub-type of 'array<S, 5>' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_ArrayOfStructOfArray) {
|
||||
// type AtomicArray = array<atomic<i32>, 5>;
|
||||
// struct S{
|
||||
// m: AtomicArray;
|
||||
// };
|
||||
// var<private> v: array<S, 5>;
|
||||
|
||||
auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
|
||||
ty.atomic(Source{{12, 34}}, ty.i32()));
|
||||
auto* s = Structure("S", {Member("m", ty.Of(atomic_array))});
|
||||
Global(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5),
|
||||
ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class\n"
|
||||
"note: atomic sub-type of 'array<S, 5>' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidStorageClass_Complex) {
|
||||
// type AtomicArray = array<atomic<i32>, 5>;
|
||||
// struct S6 { x: array<i32, 4>; };
|
||||
// struct S5 { x: S6;
|
||||
// y: AtomicArray;
|
||||
// z: array<atomic<u32>, 8>; };
|
||||
// struct S4 { x: S6;
|
||||
// y: S5;
|
||||
// z: array<atomic<i32>, 4>; };
|
||||
// struct S3 { x: S4; };
|
||||
// struct S2 { x: S3; };
|
||||
// struct S1 { x: S2; };
|
||||
// struct S0 { x: S1; };
|
||||
// var<private> g : S0;
|
||||
|
||||
auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
|
||||
ty.atomic(Source{{12, 34}}, ty.i32()));
|
||||
auto* array_i32_4 = ty.array(ty.i32(), 4);
|
||||
auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
|
||||
auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
|
||||
|
||||
auto* s6 = Structure("S6", {Member("x", array_i32_4)});
|
||||
auto* s5 = Structure("S5", {Member("x", ty.Of(s6)), //
|
||||
Member("y", ty.Of(atomic_array)), //
|
||||
Member("z", array_atomic_u32_8)}); //
|
||||
auto* s4 = Structure("S4", {Member("x", ty.Of(s6)), //
|
||||
Member("y", ty.Of(s5)), //
|
||||
Member("z", array_atomic_i32_4)}); //
|
||||
auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
|
||||
auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
|
||||
auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
|
||||
auto* s0 = Structure("S0", {Member("x", ty.Of(s1))});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables must have <storage> or <workgroup> "
|
||||
"storage class\n"
|
||||
"note: atomic sub-type of 'S0' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, Struct_AccessMode_Read) {
|
||||
auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
|
||||
{StructBlock()});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead, GroupAndBinding(0, 0));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"error: atomic variables in <storage> storage class must have read_write "
|
||||
"access mode\n"
|
||||
"note: atomic sub-type of 's' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Struct) {
|
||||
auto* s = Structure("s", {Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))},
|
||||
{StructBlock()});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead, GroupAndBinding(0, 0));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"error: atomic variables in <storage> storage class must have read_write "
|
||||
"access mode\n"
|
||||
"note: atomic sub-type of 's' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStruct) {
|
||||
// struct Inner { m : atomic<i32>; };
|
||||
// struct Outer { m : array<Inner, 4>; };
|
||||
// var<storage, read> g : Outer;
|
||||
|
||||
auto* Inner =
|
||||
Structure("Inner", {Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
|
||||
auto* Outer =
|
||||
Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead, GroupAndBinding(0, 0));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"error: atomic variables in <storage> storage class must have read_write "
|
||||
"access mode\n"
|
||||
"note: atomic sub-type of 'Outer' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStructOfArray) {
|
||||
// struct Inner { m : array<atomic<i32>, 4>; };
|
||||
// struct Outer { m : array<Inner, 4>; };
|
||||
// var<storage, read> g : Outer;
|
||||
|
||||
auto* Inner =
|
||||
Structure("Inner", {Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
|
||||
auto* Outer =
|
||||
Structure("Outer", {Member("m", ty.Of(Inner))}, {StructBlock()});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(Outer), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead, GroupAndBinding(0, 0));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables in <storage> storage class must have "
|
||||
"read_write access mode\n"
|
||||
"12:34 note: atomic sub-type of 'Outer' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Complex) {
|
||||
// type AtomicArray = array<atomic<i32>, 5>;
|
||||
// struct S6 { x: array<i32, 4>; };
|
||||
// struct S5 { x: S6;
|
||||
// y: AtomicArray;
|
||||
// z: array<atomic<u32>, 8>; };
|
||||
// struct S4 { x: S6;
|
||||
// y: S5;
|
||||
// z: array<atomic<i32>, 4>; };
|
||||
// struct S3 { x: S4; };
|
||||
// struct S2 { x: S3; };
|
||||
// struct S1 { x: S2; };
|
||||
// struct S0 { x: S1; };
|
||||
// var<storage, read> g : S0;
|
||||
|
||||
auto* atomic_array = Alias(Source{{12, 34}}, "AtomicArray",
|
||||
ty.atomic(Source{{12, 34}}, ty.i32()));
|
||||
auto* array_i32_4 = ty.array(ty.i32(), 4);
|
||||
auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8);
|
||||
auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4);
|
||||
|
||||
auto* s6 = Structure("S6", {Member("x", array_i32_4)});
|
||||
auto* s5 = Structure("S5", {Member("x", ty.Of(s6)), //
|
||||
Member("y", ty.Of(atomic_array)), //
|
||||
Member("z", array_atomic_u32_8)}); //
|
||||
auto* s4 = Structure("S4", {Member("x", ty.Of(s6)), //
|
||||
Member("y", ty.Of(s5)), //
|
||||
Member("z", array_atomic_i32_4)}); //
|
||||
auto* s3 = Structure("S3", {Member("x", ty.Of(s4))});
|
||||
auto* s2 = Structure("S2", {Member("x", ty.Of(s3))});
|
||||
auto* s1 = Structure("S1", {Member("x", ty.Of(s2))});
|
||||
auto* s0 = Structure("S0", {Member("x", ty.Of(s1))}, {StructBlock()});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s0), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead, GroupAndBinding(0, 0));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: atomic variables in <storage> storage class must have "
|
||||
"read_write access mode\n"
|
||||
"note: atomic sub-type of 'S0' is declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverAtomicValidationTest, Local) {
|
||||
WrapInFunction(Var("a", ty.atomic(Source{{12, 34}}, ty.i32())));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function variable must have a constructible type");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
1404
src/tint/resolver/attribute_validation_test.cc
Normal file
1404
src/tint/resolver/attribute_validation_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
228
src/tint/resolver/bitcast_validation_test.cc
Normal file
228
src/tint/resolver/bitcast_validation_test.cc
Normal file
@@ -0,0 +1,228 @@
|
||||
// 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/tint/ast/bitcast_expression.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
struct Type {
|
||||
template <typename T>
|
||||
static constexpr Type Create() {
|
||||
return Type{builder::DataType<T>::AST, builder::DataType<T>::Sem,
|
||||
builder::DataType<T>::Expr};
|
||||
}
|
||||
|
||||
builder::ast_type_func_ptr ast;
|
||||
builder::sem_type_func_ptr sem;
|
||||
builder::ast_expr_func_ptr expr;
|
||||
};
|
||||
|
||||
static constexpr Type kNumericScalars[] = {
|
||||
Type::Create<builder::f32>(),
|
||||
Type::Create<builder::i32>(),
|
||||
Type::Create<builder::u32>(),
|
||||
};
|
||||
static constexpr Type kVec2NumericScalars[] = {
|
||||
Type::Create<builder::vec2<builder::f32>>(),
|
||||
Type::Create<builder::vec2<builder::i32>>(),
|
||||
Type::Create<builder::vec2<builder::u32>>(),
|
||||
};
|
||||
static constexpr Type kVec3NumericScalars[] = {
|
||||
Type::Create<builder::vec3<builder::f32>>(),
|
||||
Type::Create<builder::vec3<builder::i32>>(),
|
||||
Type::Create<builder::vec3<builder::u32>>(),
|
||||
};
|
||||
static constexpr Type kVec4NumericScalars[] = {
|
||||
Type::Create<builder::vec4<builder::f32>>(),
|
||||
Type::Create<builder::vec4<builder::i32>>(),
|
||||
Type::Create<builder::vec4<builder::u32>>(),
|
||||
};
|
||||
static constexpr Type kInvalid[] = {
|
||||
// A non-exhaustive selection of uncastable types
|
||||
Type::Create<bool>(),
|
||||
Type::Create<builder::vec2<bool>>(),
|
||||
Type::Create<builder::vec3<bool>>(),
|
||||
Type::Create<builder::vec4<bool>>(),
|
||||
Type::Create<builder::array<2, builder::i32>>(),
|
||||
Type::Create<builder::array<3, builder::u32>>(),
|
||||
Type::Create<builder::array<4, builder::f32>>(),
|
||||
Type::Create<builder::array<5, bool>>(),
|
||||
Type::Create<builder::mat2x2<builder::f32>>(),
|
||||
Type::Create<builder::mat3x3<builder::f32>>(),
|
||||
Type::Create<builder::mat4x4<builder::f32>>(),
|
||||
Type::Create<builder::ptr<builder::i32>>(),
|
||||
Type::Create<builder::ptr<builder::array<2, builder::i32>>>(),
|
||||
Type::Create<builder::ptr<builder::mat2x2<builder::f32>>>(),
|
||||
};
|
||||
|
||||
using ResolverBitcastValidationTest =
|
||||
ResolverTestWithParam<std::tuple<Type, Type>>;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Valid bitcasts
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
using ResolverBitcastValidationTestPass = ResolverBitcastValidationTest;
|
||||
TEST_P(ResolverBitcastValidationTestPass, Test) {
|
||||
auto src = std::get<0>(GetParam());
|
||||
auto dst = std::get<1>(GetParam());
|
||||
|
||||
auto* cast = Bitcast(dst.ast(*this), src.expr(*this, 0));
|
||||
WrapInFunction(cast);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(TypeOf(cast), dst.sem(*this));
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(Scalars,
|
||||
ResolverBitcastValidationTestPass,
|
||||
testing::Combine(testing::ValuesIn(kNumericScalars),
|
||||
testing::ValuesIn(kNumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec2,
|
||||
ResolverBitcastValidationTestPass,
|
||||
testing::Combine(testing::ValuesIn(kVec2NumericScalars),
|
||||
testing::ValuesIn(kVec2NumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec3,
|
||||
ResolverBitcastValidationTestPass,
|
||||
testing::Combine(testing::ValuesIn(kVec3NumericScalars),
|
||||
testing::ValuesIn(kVec3NumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec4,
|
||||
ResolverBitcastValidationTestPass,
|
||||
testing::Combine(testing::ValuesIn(kVec4NumericScalars),
|
||||
testing::ValuesIn(kVec4NumericScalars)));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Invalid source type for bitcasts
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
using ResolverBitcastValidationTestInvalidSrcTy = ResolverBitcastValidationTest;
|
||||
TEST_P(ResolverBitcastValidationTestInvalidSrcTy, Test) {
|
||||
auto src = std::get<0>(GetParam());
|
||||
auto dst = std::get<1>(GetParam());
|
||||
|
||||
auto* cast = Bitcast(dst.ast(*this), Expr(Source{{12, 34}}, "src"));
|
||||
WrapInFunction(Const("src", nullptr, src.expr(*this, 0)), cast);
|
||||
|
||||
auto expected = "12:34 error: '" + src.sem(*this)->FriendlyName(Symbols()) +
|
||||
"' cannot be bitcast";
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), expected);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(Scalars,
|
||||
ResolverBitcastValidationTestInvalidSrcTy,
|
||||
testing::Combine(testing::ValuesIn(kInvalid),
|
||||
testing::ValuesIn(kNumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec2,
|
||||
ResolverBitcastValidationTestInvalidSrcTy,
|
||||
testing::Combine(testing::ValuesIn(kInvalid),
|
||||
testing::ValuesIn(kVec2NumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec3,
|
||||
ResolverBitcastValidationTestInvalidSrcTy,
|
||||
testing::Combine(testing::ValuesIn(kInvalid),
|
||||
testing::ValuesIn(kVec3NumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec4,
|
||||
ResolverBitcastValidationTestInvalidSrcTy,
|
||||
testing::Combine(testing::ValuesIn(kInvalid),
|
||||
testing::ValuesIn(kVec4NumericScalars)));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Invalid target type for bitcasts
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
using ResolverBitcastValidationTestInvalidDstTy = ResolverBitcastValidationTest;
|
||||
TEST_P(ResolverBitcastValidationTestInvalidDstTy, Test) {
|
||||
auto src = std::get<0>(GetParam());
|
||||
auto dst = std::get<1>(GetParam());
|
||||
|
||||
// Use an alias so we can put a Source on the bitcast type
|
||||
Alias("T", dst.ast(*this));
|
||||
WrapInFunction(
|
||||
Bitcast(ty.type_name(Source{{12, 34}}, "T"), src.expr(*this, 0)));
|
||||
|
||||
auto expected = "12:34 error: cannot bitcast to '" +
|
||||
dst.sem(*this)->FriendlyName(Symbols()) + "'";
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), expected);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(Scalars,
|
||||
ResolverBitcastValidationTestInvalidDstTy,
|
||||
testing::Combine(testing::ValuesIn(kNumericScalars),
|
||||
testing::ValuesIn(kInvalid)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec2,
|
||||
ResolverBitcastValidationTestInvalidDstTy,
|
||||
testing::Combine(testing::ValuesIn(kVec2NumericScalars),
|
||||
testing::ValuesIn(kInvalid)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec3,
|
||||
ResolverBitcastValidationTestInvalidDstTy,
|
||||
testing::Combine(testing::ValuesIn(kVec3NumericScalars),
|
||||
testing::ValuesIn(kInvalid)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec4,
|
||||
ResolverBitcastValidationTestInvalidDstTy,
|
||||
testing::Combine(testing::ValuesIn(kVec4NumericScalars),
|
||||
testing::ValuesIn(kInvalid)));
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Incompatible bitcast, but both src and dst types are valid
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
using ResolverBitcastValidationTestIncompatible = ResolverBitcastValidationTest;
|
||||
TEST_P(ResolverBitcastValidationTestIncompatible, Test) {
|
||||
auto src = std::get<0>(GetParam());
|
||||
auto dst = std::get<1>(GetParam());
|
||||
|
||||
WrapInFunction(Bitcast(Source{{12, 34}}, dst.ast(*this), src.expr(*this, 0)));
|
||||
|
||||
auto expected = "12:34 error: cannot bitcast from '" +
|
||||
src.sem(*this)->FriendlyName(Symbols()) + "' to '" +
|
||||
dst.sem(*this)->FriendlyName(Symbols()) + "'";
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), expected);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
ScalarToVec2,
|
||||
ResolverBitcastValidationTestIncompatible,
|
||||
testing::Combine(testing::ValuesIn(kNumericScalars),
|
||||
testing::ValuesIn(kVec2NumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec2ToVec3,
|
||||
ResolverBitcastValidationTestIncompatible,
|
||||
testing::Combine(testing::ValuesIn(kVec2NumericScalars),
|
||||
testing::ValuesIn(kVec3NumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec3ToVec4,
|
||||
ResolverBitcastValidationTestIncompatible,
|
||||
testing::Combine(testing::ValuesIn(kVec3NumericScalars),
|
||||
testing::ValuesIn(kVec4NumericScalars)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Vec4ToScalar,
|
||||
ResolverBitcastValidationTestIncompatible,
|
||||
testing::Combine(testing::ValuesIn(kVec4NumericScalars),
|
||||
testing::ValuesIn(kNumericScalars)));
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
2061
src/tint/resolver/builtin_test.cc
Normal file
2061
src/tint/resolver/builtin_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
402
src/tint/resolver/builtin_validation_test.cc
Normal file
402
src/tint/resolver/builtin_validation_test.cc
Normal file
@@ -0,0 +1,402 @@
|
||||
// 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/tint/ast/builtin_texture_helper_test.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverBuiltinValidationTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementType_void_fail) {
|
||||
// fn func { return workgroupBarrier(); }
|
||||
Func("func", {}, ty.void_(),
|
||||
{
|
||||
Return(Call(Source{Source::Location{12, 34}}, "workgroupBarrier")),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: builtin 'workgroupBarrier' does not return a value");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest, InvalidPipelineStageDirect) {
|
||||
// @stage(compute) @workgroup_size(1) fn func { return dpdx(1.0); }
|
||||
|
||||
auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
|
||||
ast::ExpressionList{Expr(1.0f)});
|
||||
Func(Source{{1, 2}}, "func", ast::VariableList{}, ty.void_(),
|
||||
{CallStmt(dpdx)},
|
||||
{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"3:4 error: built-in cannot be used by compute pipeline stage");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest, InvalidPipelineStageIndirect) {
|
||||
// fn f0 { return dpdx(1.0); }
|
||||
// fn f1 { f0(); }
|
||||
// fn f2 { f1(); }
|
||||
// @stage(compute) @workgroup_size(1) fn main { return f2(); }
|
||||
|
||||
auto* dpdx = create<ast::CallExpression>(Source{{3, 4}}, Expr("dpdx"),
|
||||
ast::ExpressionList{Expr(1.0f)});
|
||||
Func(Source{{1, 2}}, "f0", {}, ty.void_(), {CallStmt(dpdx)});
|
||||
|
||||
Func(Source{{3, 4}}, "f1", {}, ty.void_(), {CallStmt(Call("f0"))});
|
||||
|
||||
Func(Source{{5, 6}}, "f2", {}, ty.void_(), {CallStmt(Call("f1"))});
|
||||
|
||||
Func(Source{{7, 8}}, "main", {}, ty.void_(), {CallStmt(Call("f2"))},
|
||||
{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
R"(3:4 error: built-in cannot be used by compute pipeline stage
|
||||
1:2 note: called by function 'f0'
|
||||
3:4 note: called by function 'f1'
|
||||
5:6 note: called by function 'f2'
|
||||
7:8 note: called by entry point 'main')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsFunction) {
|
||||
Func(Source{{12, 34}}, "mix", {}, ty.i32(), {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a function)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsGlobalLet) {
|
||||
GlobalConst(Source{{12, 34}}, "mix", ty.i32(), Expr(1));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope let)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsGlobalVar) {
|
||||
Global(Source{{12, 34}}, "mix", ty.i32(), Expr(1),
|
||||
ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a module-scope var)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsAlias) {
|
||||
Alias(Source{{12, 34}}, "mix", ty.i32());
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: 'mix' is a builtin and cannot be redeclared as an alias)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBuiltinValidationTest, BuiltinRedeclaredAsStruct) {
|
||||
Structure(Source{{12, 34}}, "mix", {Member("m", ty.i32())});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: 'mix' is a builtin and cannot be redeclared as a struct)");
|
||||
}
|
||||
|
||||
namespace texture_constexpr_args {
|
||||
|
||||
using TextureOverloadCase = ast::builtin::test::TextureOverloadCase;
|
||||
using ValidTextureOverload = ast::builtin::test::ValidTextureOverload;
|
||||
using TextureKind = ast::builtin::test::TextureKind;
|
||||
using TextureDataType = ast::builtin::test::TextureDataType;
|
||||
using u32 = ProgramBuilder::u32;
|
||||
using i32 = ProgramBuilder::i32;
|
||||
using f32 = ProgramBuilder::f32;
|
||||
|
||||
static std::vector<TextureOverloadCase> TextureCases(
|
||||
std::unordered_set<ValidTextureOverload> overloads) {
|
||||
std::vector<TextureOverloadCase> cases;
|
||||
for (auto c : TextureOverloadCase::ValidCases()) {
|
||||
if (overloads.count(c.overload)) {
|
||||
cases.push_back(c);
|
||||
}
|
||||
}
|
||||
return cases;
|
||||
}
|
||||
|
||||
enum class Position {
|
||||
kFirst,
|
||||
kLast,
|
||||
};
|
||||
|
||||
struct Parameter {
|
||||
const char* const name;
|
||||
const Position position;
|
||||
int min;
|
||||
int max;
|
||||
};
|
||||
|
||||
class Constexpr {
|
||||
public:
|
||||
enum class Kind {
|
||||
kScalar,
|
||||
kVec2,
|
||||
kVec3,
|
||||
kVec3_Scalar_Vec2,
|
||||
kVec3_Vec2_Scalar,
|
||||
kEmptyVec2,
|
||||
kEmptyVec3,
|
||||
};
|
||||
|
||||
Constexpr(int32_t invalid_idx,
|
||||
Kind k,
|
||||
int32_t x = 0,
|
||||
int32_t y = 0,
|
||||
int32_t z = 0)
|
||||
: invalid_index(invalid_idx), kind(k), values{x, y, z} {}
|
||||
|
||||
const ast::Expression* operator()(Source src, ProgramBuilder& b) {
|
||||
switch (kind) {
|
||||
case Kind::kScalar:
|
||||
return b.Expr(src, values[0]);
|
||||
case Kind::kVec2:
|
||||
return b.Construct(src, b.ty.vec2<i32>(), values[0], values[1]);
|
||||
case Kind::kVec3:
|
||||
return b.Construct(src, b.ty.vec3<i32>(), values[0], values[1],
|
||||
values[2]);
|
||||
case Kind::kVec3_Scalar_Vec2:
|
||||
return b.Construct(src, b.ty.vec3<i32>(), values[0],
|
||||
b.vec2<i32>(values[1], values[2]));
|
||||
case Kind::kVec3_Vec2_Scalar:
|
||||
return b.Construct(src, b.ty.vec3<i32>(),
|
||||
b.vec2<i32>(values[0], values[1]), values[2]);
|
||||
case Kind::kEmptyVec2:
|
||||
return b.Construct(src, b.ty.vec2<i32>());
|
||||
case Kind::kEmptyVec3:
|
||||
return b.Construct(src, b.ty.vec3<i32>());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const constexpr int32_t kValid = -1;
|
||||
const int32_t invalid_index; // Expected error value, or kValid
|
||||
const Kind kind;
|
||||
const std::array<int32_t, 3> values;
|
||||
};
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, Parameter param) {
|
||||
return out << param.name;
|
||||
}
|
||||
|
||||
static std::ostream& operator<<(std::ostream& out, Constexpr expr) {
|
||||
switch (expr.kind) {
|
||||
case Constexpr::Kind::kScalar:
|
||||
return out << expr.values[0];
|
||||
case Constexpr::Kind::kVec2:
|
||||
return out << "vec2(" << expr.values[0] << ", " << expr.values[1] << ")";
|
||||
case Constexpr::Kind::kVec3:
|
||||
return out << "vec3(" << expr.values[0] << ", " << expr.values[1] << ", "
|
||||
<< expr.values[2] << ")";
|
||||
case Constexpr::Kind::kVec3_Scalar_Vec2:
|
||||
return out << "vec3(" << expr.values[0] << ", vec2(" << expr.values[1]
|
||||
<< ", " << expr.values[2] << "))";
|
||||
case Constexpr::Kind::kVec3_Vec2_Scalar:
|
||||
return out << "vec3(vec2(" << expr.values[0] << ", " << expr.values[1]
|
||||
<< "), " << expr.values[2] << ")";
|
||||
case Constexpr::Kind::kEmptyVec2:
|
||||
return out << "vec2()";
|
||||
case Constexpr::Kind::kEmptyVec3:
|
||||
return out << "vec3()";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
using BuiltinTextureConstExprArgValidationTest = ResolverTestWithParam<
|
||||
std::tuple<TextureOverloadCase, Parameter, Constexpr>>;
|
||||
|
||||
TEST_P(BuiltinTextureConstExprArgValidationTest, Immediate) {
|
||||
auto& p = GetParam();
|
||||
auto overload = std::get<0>(p);
|
||||
auto param = std::get<1>(p);
|
||||
auto expr = std::get<2>(p);
|
||||
|
||||
overload.BuildTextureVariable(this);
|
||||
overload.BuildSamplerVariable(this);
|
||||
|
||||
auto args = overload.args(this);
|
||||
auto*& arg_to_replace =
|
||||
(param.position == Position::kFirst) ? args.front() : args.back();
|
||||
|
||||
// BuildTextureVariable() uses a Literal for scalars, and a CallExpression for
|
||||
// a vector constructor.
|
||||
bool is_vector = arg_to_replace->Is<ast::CallExpression>();
|
||||
|
||||
// Make the expression to be replaced, reachable. This keeps the resolver
|
||||
// happy.
|
||||
WrapInFunction(arg_to_replace);
|
||||
|
||||
arg_to_replace = expr(Source{{12, 34}}, *this);
|
||||
|
||||
// Call the builtin with the constexpr argument replaced
|
||||
Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
if (expr.invalid_index == Constexpr::kValid) {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
} else {
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
std::stringstream err;
|
||||
if (is_vector) {
|
||||
err << "12:34 error: each component of the " << param.name
|
||||
<< " argument must be at least " << param.min << " and at most "
|
||||
<< param.max << ". " << param.name << " component "
|
||||
<< expr.invalid_index << " is "
|
||||
<< std::to_string(expr.values[expr.invalid_index]);
|
||||
} else {
|
||||
err << "12:34 error: the " << param.name << " argument must be at least "
|
||||
<< param.min << " and at most " << param.max << ". " << param.name
|
||||
<< " is " << std::to_string(expr.values[expr.invalid_index]);
|
||||
}
|
||||
EXPECT_EQ(r()->error(), err.str());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(BuiltinTextureConstExprArgValidationTest, GlobalConst) {
|
||||
auto& p = GetParam();
|
||||
auto overload = std::get<0>(p);
|
||||
auto param = std::get<1>(p);
|
||||
auto expr = std::get<2>(p);
|
||||
|
||||
// Build the global texture and sampler variables
|
||||
overload.BuildTextureVariable(this);
|
||||
overload.BuildSamplerVariable(this);
|
||||
|
||||
// Build the module-scope let 'G' with the offset value
|
||||
GlobalConst("G", nullptr, expr({}, *this));
|
||||
|
||||
auto args = overload.args(this);
|
||||
auto*& arg_to_replace =
|
||||
(param.position == Position::kFirst) ? args.front() : args.back();
|
||||
|
||||
// Make the expression to be replaced, reachable. This keeps the resolver
|
||||
// happy.
|
||||
WrapInFunction(arg_to_replace);
|
||||
|
||||
arg_to_replace = Expr(Source{{12, 34}}, "G");
|
||||
|
||||
// Call the builtin with the constexpr argument replaced
|
||||
Func("func", {}, ty.void_(), {CallStmt(Call(overload.function, args))},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
std::stringstream err;
|
||||
err << "12:34 error: the " << param.name
|
||||
<< " argument must be a const_expression";
|
||||
EXPECT_EQ(r()->error(), err.str());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Offset2D,
|
||||
BuiltinTextureConstExprArgValidationTest,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(TextureCases({
|
||||
ValidTextureOverload::kSample2dOffsetF32,
|
||||
ValidTextureOverload::kSample2dArrayOffsetF32,
|
||||
ValidTextureOverload::kSampleDepth2dOffsetF32,
|
||||
ValidTextureOverload::kSampleDepth2dArrayOffsetF32,
|
||||
ValidTextureOverload::kSampleBias2dOffsetF32,
|
||||
ValidTextureOverload::kSampleBias2dArrayOffsetF32,
|
||||
ValidTextureOverload::kSampleLevel2dOffsetF32,
|
||||
ValidTextureOverload::kSampleLevel2dArrayOffsetF32,
|
||||
ValidTextureOverload::kSampleLevelDepth2dOffsetF32,
|
||||
ValidTextureOverload::kSampleLevelDepth2dArrayOffsetF32,
|
||||
ValidTextureOverload::kSampleGrad2dOffsetF32,
|
||||
ValidTextureOverload::kSampleGrad2dArrayOffsetF32,
|
||||
ValidTextureOverload::kSampleCompareDepth2dOffsetF32,
|
||||
ValidTextureOverload::kSampleCompareDepth2dArrayOffsetF32,
|
||||
ValidTextureOverload::kSampleCompareLevelDepth2dOffsetF32,
|
||||
ValidTextureOverload::kSampleCompareLevelDepth2dArrayOffsetF32,
|
||||
})),
|
||||
testing::Values(Parameter{"offset", Position::kLast, -8, 7}),
|
||||
testing::Values(
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kEmptyVec2},
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kVec2, -1, 1},
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kVec2, 7, -8},
|
||||
Constexpr{0, Constexpr::Kind::kVec2, 8, 0},
|
||||
Constexpr{1, Constexpr::Kind::kVec2, 0, 8},
|
||||
Constexpr{0, Constexpr::Kind::kVec2, -9, 0},
|
||||
Constexpr{1, Constexpr::Kind::kVec2, 0, -9},
|
||||
Constexpr{0, Constexpr::Kind::kVec2, 8, 8},
|
||||
Constexpr{0, Constexpr::Kind::kVec2, -9, -9})));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Offset3D,
|
||||
BuiltinTextureConstExprArgValidationTest,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(TextureCases({
|
||||
ValidTextureOverload::kSample3dOffsetF32,
|
||||
ValidTextureOverload::kSampleBias3dOffsetF32,
|
||||
ValidTextureOverload::kSampleLevel3dOffsetF32,
|
||||
ValidTextureOverload::kSampleGrad3dOffsetF32,
|
||||
})),
|
||||
testing::Values(Parameter{"offset", Position::kLast, -8, 7}),
|
||||
testing::Values(
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kEmptyVec3},
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kVec3, 0, 0, 0},
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kVec3, 7, -8, 7},
|
||||
Constexpr{0, Constexpr::Kind::kVec3, 10, 0, 0},
|
||||
Constexpr{1, Constexpr::Kind::kVec3, 0, 10, 0},
|
||||
Constexpr{2, Constexpr::Kind::kVec3, 0, 0, 10},
|
||||
Constexpr{0, Constexpr::Kind::kVec3, 10, 11, 12},
|
||||
Constexpr{0, Constexpr::Kind::kVec3_Scalar_Vec2, 10, 0, 0},
|
||||
Constexpr{1, Constexpr::Kind::kVec3_Scalar_Vec2, 0, 10, 0},
|
||||
Constexpr{2, Constexpr::Kind::kVec3_Scalar_Vec2, 0, 0, 10},
|
||||
Constexpr{0, Constexpr::Kind::kVec3_Scalar_Vec2, 10, 11, 12},
|
||||
Constexpr{0, Constexpr::Kind::kVec3_Vec2_Scalar, 10, 0, 0},
|
||||
Constexpr{1, Constexpr::Kind::kVec3_Vec2_Scalar, 0, 10, 0},
|
||||
Constexpr{2, Constexpr::Kind::kVec3_Vec2_Scalar, 0, 0, 10},
|
||||
Constexpr{0, Constexpr::Kind::kVec3_Vec2_Scalar, 10, 11, 12})));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
Component,
|
||||
BuiltinTextureConstExprArgValidationTest,
|
||||
testing::Combine(
|
||||
testing::ValuesIn(
|
||||
TextureCases({ValidTextureOverload::kGather2dF32,
|
||||
ValidTextureOverload::kGather2dOffsetF32,
|
||||
ValidTextureOverload::kGather2dArrayF32,
|
||||
ValidTextureOverload::kGather2dArrayOffsetF32,
|
||||
ValidTextureOverload::kGatherCubeF32,
|
||||
ValidTextureOverload::kGatherCubeArrayF32})),
|
||||
testing::Values(Parameter{"component", Position::kFirst, 0, 3}),
|
||||
testing::Values(
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 0},
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 1},
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 2},
|
||||
Constexpr{Constexpr::kValid, Constexpr::Kind::kScalar, 3},
|
||||
Constexpr{0, Constexpr::Kind::kScalar, 4},
|
||||
Constexpr{0, Constexpr::Kind::kScalar, 123},
|
||||
Constexpr{0, Constexpr::Kind::kScalar, -1})));
|
||||
|
||||
} // namespace texture_constexpr_args
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
1292
src/tint/resolver/builtins_validation_test.cc
Normal file
1292
src/tint/resolver/builtins_validation_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
118
src/tint/resolver/call_test.cc
Normal file
118
src/tint/resolver/call_test.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/call_statement.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
// Helpers and typedefs
|
||||
template <typename T>
|
||||
using DataType = builder::DataType<T>;
|
||||
template <int N, typename T>
|
||||
using vec = builder::vec<N, 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 <int N, int M, typename T>
|
||||
using mat = builder::mat<N, M, T>;
|
||||
template <typename T>
|
||||
using mat2x2 = builder::mat2x2<T>;
|
||||
template <typename T>
|
||||
using mat2x3 = builder::mat2x3<T>;
|
||||
template <typename T>
|
||||
using mat3x2 = builder::mat3x2<T>;
|
||||
template <typename T>
|
||||
using mat3x3 = builder::mat3x3<T>;
|
||||
template <typename T>
|
||||
using mat4x4 = builder::mat4x4<T>;
|
||||
template <typename T, int ID = 0>
|
||||
using alias = builder::alias<T, ID>;
|
||||
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;
|
||||
|
||||
using ResolverCallTest = ResolverTest;
|
||||
|
||||
struct Params {
|
||||
builder::ast_expr_func_ptr create_value;
|
||||
builder::ast_type_func_ptr create_type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr Params ParamsFor() {
|
||||
return Params{DataType<T>::Expr, DataType<T>::AST};
|
||||
}
|
||||
|
||||
static constexpr Params all_param_types[] = {
|
||||
ParamsFor<bool>(), //
|
||||
ParamsFor<u32>(), //
|
||||
ParamsFor<i32>(), //
|
||||
ParamsFor<f32>(), //
|
||||
ParamsFor<vec3<bool>>(), //
|
||||
ParamsFor<vec3<i32>>(), //
|
||||
ParamsFor<vec3<u32>>(), //
|
||||
ParamsFor<vec3<f32>>(), //
|
||||
ParamsFor<mat3x3<f32>>(), //
|
||||
ParamsFor<mat2x3<f32>>(), //
|
||||
ParamsFor<mat3x2<f32>>() //
|
||||
};
|
||||
|
||||
TEST_F(ResolverCallTest, Valid) {
|
||||
ast::VariableList params;
|
||||
ast::ExpressionList args;
|
||||
for (auto& p : all_param_types) {
|
||||
params.push_back(Param(Sym(), p.create_type(*this)));
|
||||
args.push_back(p.create_value(*this, 0));
|
||||
}
|
||||
|
||||
auto* func = Func("foo", std::move(params), ty.f32(), {Return(1.23f)});
|
||||
auto* call_expr = Call("foo", std::move(args));
|
||||
WrapInFunction(call_expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* call = Sem().Get(call_expr);
|
||||
EXPECT_NE(call, nullptr);
|
||||
EXPECT_EQ(call->Target(), Sem().Get(func));
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallTest, OutOfOrder) {
|
||||
auto* call_expr = Call("b");
|
||||
Func("a", {}, ty.void_(), {CallStmt(call_expr)});
|
||||
auto* b = Func("b", {}, ty.void_(), {});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* call = Sem().Get(call_expr);
|
||||
EXPECT_NE(call, nullptr);
|
||||
EXPECT_EQ(call->Target(), Sem().Get(b));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
288
src/tint/resolver/call_validation_test.cc
Normal file
288
src/tint/resolver/call_validation_test.cc
Normal file
@@ -0,0 +1,288 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/call_statement.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverCallValidationTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverCallValidationTest, TooFewArgs) {
|
||||
Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
|
||||
{Return()});
|
||||
auto* call = Call(Source{{12, 34}}, "foo", 1);
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: too few arguments in call to 'foo', expected 2, got 1");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, TooManyArgs) {
|
||||
Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
|
||||
{Return()});
|
||||
auto* call = Call(Source{{12, 34}}, "foo", 1, 1.0f, 1.0f);
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: too many arguments in call to 'foo', expected 2, got 3");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, MismatchedArgs) {
|
||||
Func("foo", {Param(Sym(), ty.i32()), Param(Sym(), ty.f32())}, ty.void_(),
|
||||
{Return()});
|
||||
auto* call = Call("foo", Expr(Source{{12, 34}}, true), 1.0f);
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: type mismatch for argument 1 in call to 'foo', "
|
||||
"expected 'i32', got 'bool'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, UnusedRetval) {
|
||||
// fn func() -> f32 { return 1.0; }
|
||||
// fn main() {func(); return; }
|
||||
|
||||
Func("func", {}, ty.f32(), {Return(Expr(1.0f))}, {});
|
||||
|
||||
Func("main", {}, ty.void_(),
|
||||
{
|
||||
CallStmt(Source{{12, 34}}, Call("func")),
|
||||
Return(),
|
||||
});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, PointerArgument_VariableIdentExpr) {
|
||||
// fn foo(p: ptr<function, i32>) {}
|
||||
// fn main() {
|
||||
// var z: i32 = 1;
|
||||
// foo(&z);
|
||||
// }
|
||||
auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
|
||||
Func("foo", {param}, ty.void_(), {});
|
||||
Func("main", {}, ty.void_(),
|
||||
{
|
||||
Decl(Var("z", ty.i32(), Expr(1))),
|
||||
CallStmt(Call("foo", AddressOf(Source{{12, 34}}, Expr("z")))),
|
||||
});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, PointerArgument_ConstIdentExpr) {
|
||||
// fn foo(p: ptr<function, i32>) {}
|
||||
// fn main() {
|
||||
// let z: i32 = 1;
|
||||
// foo(&z);
|
||||
// }
|
||||
auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
|
||||
Func("foo", {param}, ty.void_(), {});
|
||||
Func("main", {}, ty.void_(),
|
||||
{
|
||||
Decl(Const("z", ty.i32(), Expr(1))),
|
||||
CallStmt(Call("foo", AddressOf(Expr(Source{{12, 34}}, "z")))),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, PointerArgument_NotIdentExprVar) {
|
||||
// struct S { m: i32; };
|
||||
// fn foo(p: ptr<function, i32>) {}
|
||||
// fn main() {
|
||||
// var v: S;
|
||||
// foo(&v.m);
|
||||
// }
|
||||
auto* S = Structure("S", {Member("m", ty.i32())});
|
||||
auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
|
||||
Func("foo", {param}, ty.void_(), {});
|
||||
Func("main", {}, ty.void_(),
|
||||
{
|
||||
Decl(Var("v", ty.Of(S))),
|
||||
CallStmt(Call(
|
||||
"foo", AddressOf(Source{{12, 34}}, MemberAccessor("v", "m")))),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: expected an address-of expression of a variable "
|
||||
"identifier expression or a function parameter");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, PointerArgument_AddressOfMemberAccessor) {
|
||||
// struct S { m: i32; };
|
||||
// fn foo(p: ptr<function, i32>) {}
|
||||
// fn main() {
|
||||
// let v: S = S();
|
||||
// foo(&v.m);
|
||||
// }
|
||||
auto* S = Structure("S", {Member("m", ty.i32())});
|
||||
auto* param = Param("p", ty.pointer<i32>(ast::StorageClass::kFunction));
|
||||
Func("foo", {param}, ty.void_(), {});
|
||||
Func("main", {}, ty.void_(),
|
||||
{
|
||||
Decl(Const("v", ty.Of(S), Construct(ty.Of(S)))),
|
||||
CallStmt(Call("foo", AddressOf(Expr(Source{{12, 34}},
|
||||
MemberAccessor("v", "m"))))),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, PointerArgument_FunctionParam) {
|
||||
// fn foo(p: ptr<function, i32>) {}
|
||||
// fn bar(p: ptr<function, i32>) {
|
||||
// foo(p);
|
||||
// }
|
||||
Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
|
||||
ty.void_(), {});
|
||||
Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
|
||||
ty.void_(), ast::StatementList{CallStmt(Call("foo", Expr("p")))});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, PointerArgument_FunctionParamWithMain) {
|
||||
// fn foo(p: ptr<function, i32>) {}
|
||||
// fn bar(p: ptr<function, i32>) {
|
||||
// foo(p);
|
||||
// }
|
||||
// @stage(fragment)
|
||||
// fn main() {
|
||||
// var v: i32;
|
||||
// bar(&v);
|
||||
// }
|
||||
Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
|
||||
ty.void_(), {});
|
||||
Func("bar", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
|
||||
ty.void_(), ast::StatementList{CallStmt(Call("foo", Expr("p")))});
|
||||
Func("main", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(Var("v", ty.i32(), Expr(1))),
|
||||
CallStmt(Call("foo", AddressOf(Expr("v")))),
|
||||
},
|
||||
{
|
||||
Stage(ast::PipelineStage::kFragment),
|
||||
});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, LetPointer) {
|
||||
// fn x(p : ptr<function, i32>) -> i32 {}
|
||||
// @stage(fragment)
|
||||
// fn main() {
|
||||
// var v: i32;
|
||||
// let p: ptr<function, i32> = &v;
|
||||
// var c: i32 = x(p);
|
||||
// }
|
||||
Func("x", {Param("p", ty.pointer<i32>(ast::StorageClass::kFunction))},
|
||||
ty.void_(), {});
|
||||
auto* v = Var("v", ty.i32());
|
||||
auto* p = Const("p", ty.pointer(ty.i32(), ast::StorageClass::kFunction),
|
||||
AddressOf(v));
|
||||
auto* c = Var("c", ty.i32(), ast::StorageClass::kNone,
|
||||
Call("x", Expr(Source{{12, 34}}, p)));
|
||||
Func("main", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(v),
|
||||
Decl(p),
|
||||
Decl(c),
|
||||
},
|
||||
{
|
||||
Stage(ast::PipelineStage::kFragment),
|
||||
});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: expected an address-of expression of a variable "
|
||||
"identifier expression or a function parameter");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, LetPointerPrivate) {
|
||||
// let p: ptr<private, i32> = &v;
|
||||
// fn foo(p : ptr<private, i32>) -> i32 {}
|
||||
// var v: i32;
|
||||
// @stage(fragment)
|
||||
// fn main() {
|
||||
// var c: i32 = foo(p);
|
||||
// }
|
||||
Func("foo", {Param("p", ty.pointer<i32>(ast::StorageClass::kPrivate))},
|
||||
ty.void_(), {});
|
||||
auto* v = Global("v", ty.i32(), ast::StorageClass::kPrivate);
|
||||
auto* p = Const("p", ty.pointer(ty.i32(), ast::StorageClass::kPrivate),
|
||||
AddressOf(v));
|
||||
auto* c = Var("c", ty.i32(), ast::StorageClass::kNone,
|
||||
Call("foo", Expr(Source{{12, 34}}, p)));
|
||||
Func("main", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
Decl(p),
|
||||
Decl(c),
|
||||
},
|
||||
{
|
||||
Stage(ast::PipelineStage::kFragment),
|
||||
});
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: expected an address-of expression of a variable "
|
||||
"identifier expression or a function parameter");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, CallVariable) {
|
||||
// var v : i32;
|
||||
// fn f() {
|
||||
// v();
|
||||
// }
|
||||
Global("v", ty.i32(), ast::StorageClass::kPrivate);
|
||||
Func("f", {}, ty.void_(), {CallStmt(Call(Source{{12, 34}}, "v"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(error: cannot call variable 'v'
|
||||
note: 'v' declared here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverCallValidationTest, CallVariableShadowsFunction) {
|
||||
// fn x() {}
|
||||
// fn f() {
|
||||
// var x : i32;
|
||||
// x();
|
||||
// }
|
||||
Func("x", {}, ty.void_(), {});
|
||||
Func("f", {}, ty.void_(),
|
||||
{
|
||||
Decl(Var(Source{{56, 78}}, "x", ty.i32())),
|
||||
CallStmt(Call(Source{{12, 34}}, "x")),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(error: cannot call variable 'x'
|
||||
56:78 note: 'x' declared here)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
380
src/tint/resolver/compound_statement_test.cc
Normal file
380
src/tint/resolver/compound_statement_test.cc
Normal file
@@ -0,0 +1,380 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/block_statement.h"
|
||||
#include "src/tint/sem/for_loop_statement.h"
|
||||
#include "src/tint/sem/if_statement.h"
|
||||
#include "src/tint/sem/loop_statement.h"
|
||||
#include "src/tint/sem/switch_statement.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverCompoundStatementTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverCompoundStatementTest, FunctionBlock) {
|
||||
// fn F() {
|
||||
// var x : 32;
|
||||
// }
|
||||
auto* stmt = Decl(Var("x", ty.i32()));
|
||||
auto* f = Func("F", {}, ty.void_(), {stmt});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* s = Sem().Get(stmt);
|
||||
ASSERT_NE(s, nullptr);
|
||||
ASSERT_NE(s->Block(), nullptr);
|
||||
ASSERT_TRUE(s->Block()->Is<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Function()->Declaration(), f);
|
||||
EXPECT_EQ(s->Block()->Parent(), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ResolverCompoundStatementTest, Block) {
|
||||
// fn F() {
|
||||
// {
|
||||
// var x : 32;
|
||||
// }
|
||||
// }
|
||||
auto* stmt = Decl(Var("x", ty.i32()));
|
||||
auto* block = Block(stmt);
|
||||
auto* f = Func("F", {}, ty.void_(), {block});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
{
|
||||
auto* s = Sem().Get(block);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::BlockStatement>());
|
||||
EXPECT_EQ(s, s->Block());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt);
|
||||
ASSERT_NE(s, nullptr);
|
||||
ASSERT_NE(s->Block(), nullptr);
|
||||
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Block()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
ASSERT_TRUE(s->Block()->Parent()->Is<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Function()->Declaration(), f);
|
||||
EXPECT_EQ(s->Block()->Parent()->Parent(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ResolverCompoundStatementTest, Loop) {
|
||||
// fn F() {
|
||||
// loop {
|
||||
// break;
|
||||
// continuing {
|
||||
// stmt;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
auto* brk = Break();
|
||||
auto* stmt = Ignore(1);
|
||||
auto* loop = Loop(Block(brk), Block(stmt));
|
||||
auto* f = Func("F", {}, ty.void_(), {loop});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
{
|
||||
auto* s = Sem().Get(loop);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::LoopStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(brk);
|
||||
ASSERT_NE(s, nullptr);
|
||||
ASSERT_NE(s->Block(), nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::LoopBlockStatement>());
|
||||
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::LoopStatement>());
|
||||
EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()));
|
||||
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_TRUE(
|
||||
Is<sem::FunctionBlockStatement>(s->Parent()->Parent()->Parent()));
|
||||
|
||||
EXPECT_EQ(s->Function()->Declaration(), f);
|
||||
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(), nullptr);
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt);
|
||||
ASSERT_NE(s, nullptr);
|
||||
ASSERT_NE(s->Block(), nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
|
||||
EXPECT_EQ(s->Parent(),
|
||||
s->FindFirstParent<sem::LoopContinuingBlockStatement>());
|
||||
EXPECT_TRUE(Is<sem::LoopContinuingBlockStatement>(s->Parent()));
|
||||
|
||||
EXPECT_EQ(s->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::LoopBlockStatement>());
|
||||
EXPECT_TRUE(Is<sem::LoopBlockStatement>(s->Parent()->Parent()));
|
||||
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::LoopStatement>());
|
||||
EXPECT_TRUE(Is<sem::LoopStatement>(s->Parent()->Parent()->Parent()));
|
||||
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(
|
||||
s->Parent()->Parent()->Parent()->Parent()));
|
||||
EXPECT_EQ(s->Function()->Declaration(), f);
|
||||
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent()->Parent(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ResolverCompoundStatementTest, ForLoop) {
|
||||
// fn F() {
|
||||
// for (var i : u32; true; i = i + 1u) {
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
auto* init = Decl(Var("i", ty.u32()));
|
||||
auto* cond = Expr(true);
|
||||
auto* cont = Assign("i", Add("i", 1u));
|
||||
auto* stmt = Return();
|
||||
auto* body = Block(stmt);
|
||||
auto* for_ = For(init, cond, cont, body);
|
||||
auto* f = Func("F", {}, ty.void_(), {for_});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
{
|
||||
auto* s = Sem().Get(for_);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::ForLoopStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(init);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
|
||||
EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
|
||||
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
|
||||
}
|
||||
{ // Condition expression's statement is the for-loop itself
|
||||
auto* e = Sem().Get(cond);
|
||||
ASSERT_NE(e, nullptr);
|
||||
auto* s = e->Stmt();
|
||||
ASSERT_NE(s, nullptr);
|
||||
ASSERT_TRUE(Is<sem::ForLoopStatement>(s));
|
||||
ASSERT_NE(s->Parent(), nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Block()));
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(cont);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::ForLoopStatement>());
|
||||
EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()));
|
||||
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_TRUE(Is<sem::FunctionBlockStatement>(s->Parent()->Parent()));
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt);
|
||||
ASSERT_NE(s, nullptr);
|
||||
ASSERT_NE(s->Block(), nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Block(), s->FindFirstParent<sem::LoopBlockStatement>());
|
||||
EXPECT_TRUE(Is<sem::ForLoopStatement>(s->Parent()->Parent()));
|
||||
EXPECT_EQ(s->Block()->Parent(),
|
||||
s->FindFirstParent<sem::ForLoopStatement>());
|
||||
ASSERT_TRUE(
|
||||
Is<sem::FunctionBlockStatement>(s->Block()->Parent()->Parent()));
|
||||
EXPECT_EQ(s->Block()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Function()->Declaration(), f);
|
||||
EXPECT_EQ(s->Block()->Parent()->Parent()->Parent(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ResolverCompoundStatementTest, If) {
|
||||
// fn F() {
|
||||
// if (cond_a) {
|
||||
// stat_a;
|
||||
// } else if (cond_b) {
|
||||
// stat_b;
|
||||
// } else {
|
||||
// stat_c;
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* cond_a = Expr(true);
|
||||
auto* stmt_a = Ignore(1);
|
||||
auto* cond_b = Expr(true);
|
||||
auto* stmt_b = Ignore(1);
|
||||
auto* stmt_c = Ignore(1);
|
||||
auto* if_stmt = If(cond_a, Block(stmt_a), Else(cond_b, Block(stmt_b)),
|
||||
Else(nullptr, Block(stmt_c)));
|
||||
WrapInFunction(if_stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
{
|
||||
auto* s = Sem().Get(if_stmt);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::IfStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
}
|
||||
{
|
||||
auto* e = Sem().Get(cond_a);
|
||||
ASSERT_NE(e, nullptr);
|
||||
auto* s = e->Stmt();
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::IfStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt_a);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::IfStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
}
|
||||
{
|
||||
auto* e = Sem().Get(cond_b);
|
||||
ASSERT_NE(e, nullptr);
|
||||
auto* s = e->Stmt();
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::ElseStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::IfStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->Block());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt_b);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::IfStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt_c);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::ElseStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::IfStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ResolverCompoundStatementTest, Switch) {
|
||||
// fn F() {
|
||||
// switch (expr) {
|
||||
// case 1: {
|
||||
// stmt_a;
|
||||
// }
|
||||
// case 2: {
|
||||
// stmt_b;
|
||||
// }
|
||||
// default: {
|
||||
// stmt_c;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* expr = Expr(5);
|
||||
auto* stmt_a = Ignore(1);
|
||||
auto* stmt_b = Ignore(1);
|
||||
auto* stmt_c = Ignore(1);
|
||||
auto* swi = Switch(expr, Case(Expr(1), Block(stmt_a)),
|
||||
Case(Expr(2), Block(stmt_b)), DefaultCase(Block(stmt_c)));
|
||||
WrapInFunction(swi);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
{
|
||||
auto* s = Sem().Get(swi);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::SwitchStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
}
|
||||
{
|
||||
auto* e = Sem().Get(expr);
|
||||
ASSERT_NE(e, nullptr);
|
||||
auto* s = e->Stmt();
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_TRUE(s->Is<sem::SwitchStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt_a);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::SwitchStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt_b);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::SwitchStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
}
|
||||
{
|
||||
auto* s = Sem().Get(stmt_c);
|
||||
ASSERT_NE(s, nullptr);
|
||||
EXPECT_EQ(s->Parent(), s->FindFirstParent<sem::BlockStatement>());
|
||||
EXPECT_EQ(s->Parent(), s->Block());
|
||||
EXPECT_EQ(s->Parent()->Parent(), s->FindFirstParent<sem::CaseStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::SwitchStatement>());
|
||||
EXPECT_EQ(s->Parent()->Parent()->Parent()->Parent(),
|
||||
s->FindFirstParent<sem::FunctionBlockStatement>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
364
src/tint/resolver/control_block_validation_test.cc
Normal file
364
src/tint/resolver/control_block_validation_test.cc
Normal file
@@ -0,0 +1,364 @@
|
||||
// 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/tint/ast/break_statement.h"
|
||||
#include "src/tint/ast/continue_statement.h"
|
||||
#include "src/tint/ast/fallthrough_statement.h"
|
||||
#include "src/tint/ast/switch_statement.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
namespace tint {
|
||||
namespace {
|
||||
|
||||
class ResolverControlBlockValidationTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
SwitchSelectorExpressionNoneIntegerType_Fail) {
|
||||
// var a : f32 = 3.14;
|
||||
// switch (a) {
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.f32(), Expr(3.14f));
|
||||
|
||||
auto* block = Block(Decl(var), Switch(Expr(Source{{12, 34}}, "a"), //
|
||||
DefaultCase()));
|
||||
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: switch statement selector expression must be of a "
|
||||
"scalar integer type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, SwitchWithoutDefault_Fail) {
|
||||
// var a : i32 = 2;
|
||||
// switch (a) {
|
||||
// case 1: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch(Source{{12, 34}}, "a", //
|
||||
Case(Expr(1))));
|
||||
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: switch statement must have a default clause");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, SwitchWithTwoDefault_Fail) {
|
||||
// var a : i32 = 2;
|
||||
// switch (a) {
|
||||
// default: {}
|
||||
// case 1: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
DefaultCase(), //
|
||||
Case(Expr(1)), //
|
||||
DefaultCase(Source{{12, 34}})));
|
||||
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: switch statement must have exactly one default clause");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_Loop_continue) {
|
||||
// loop {
|
||||
// if (false) { break; }
|
||||
// var z: i32;
|
||||
// continue;
|
||||
// z = 1;
|
||||
// }
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(
|
||||
Loop(Block(If(false, Block(Break())), decl_z, cont, assign_z)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
UnreachableCode_Loop_continue_InBlocks) {
|
||||
// loop {
|
||||
// if (false) { break; }
|
||||
// var z: i32;
|
||||
// {{{continue;}}}
|
||||
// z = 1;
|
||||
// }
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(Loop(Block(If(false, Block(Break())), decl_z,
|
||||
Block(Block(Block(cont))), assign_z)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_ForLoop_continue) {
|
||||
// for (;false;) {
|
||||
// var z: i32;
|
||||
// continue;
|
||||
// z = 1;
|
||||
// }
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(For(nullptr, false, nullptr, //
|
||||
Block(decl_z, cont, assign_z)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
UnreachableCode_ForLoop_continue_InBlocks) {
|
||||
// for (;false;) {
|
||||
// var z: i32;
|
||||
// {{{continue;}}}
|
||||
// z = 1;
|
||||
// }
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* cont = Continue();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(For(nullptr, false, nullptr,
|
||||
Block(decl_z, Block(Block(Block(cont))), assign_z)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(cont)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break) {
|
||||
// switch (1) {
|
||||
// case 1: {
|
||||
// var z: i32;
|
||||
// break;
|
||||
// z = 1;
|
||||
// default: {}
|
||||
// }
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* brk = Break();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction( //
|
||||
Block(Switch(1, //
|
||||
Case(Expr(1), Block(decl_z, brk, assign_z)), //
|
||||
DefaultCase())));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(brk)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, UnreachableCode_break_InBlocks) {
|
||||
// loop {
|
||||
// switch (1) {
|
||||
// case 1: { {{{break;}}} var a : u32 = 2;}
|
||||
// default: {}
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
auto* decl_z = Decl(Var("z", ty.i32()));
|
||||
auto* brk = Break();
|
||||
auto* assign_z = Assign(Source{{12, 34}}, "z", 1);
|
||||
WrapInFunction(Loop(Block(
|
||||
Switch(1, //
|
||||
Case(Expr(1), Block(decl_z, Block(Block(Block(brk))), assign_z)),
|
||||
DefaultCase()), //
|
||||
Break())));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_z)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(brk)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_z)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
SwitchConditionTypeMustMatchSelectorType2_Fail) {
|
||||
// var a : u32 = 2;
|
||||
// switch (a) {
|
||||
// case 1: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), Switch("a", //
|
||||
Case(Source{{12, 34}}, {Expr(1u)}), //
|
||||
DefaultCase()));
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: the case selector values must have the same type as "
|
||||
"the selector expression.");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
SwitchConditionTypeMustMatchSelectorType_Fail) {
|
||||
// var a : u32 = 2;
|
||||
// switch (a) {
|
||||
// case -1: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.u32(), Expr(2u));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
Case(Source{{12, 34}}, {Expr(-1)}), //
|
||||
DefaultCase()));
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: the case selector values must have the same type as "
|
||||
"the selector expression.");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
NonUniqueCaseSelectorValueUint_Fail) {
|
||||
// var a : u32 = 3;
|
||||
// switch (a) {
|
||||
// case 0u: {}
|
||||
// case 2u, 3u, 2u: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.u32(), Expr(3u));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
Case(Expr(0u)),
|
||||
Case({
|
||||
Expr(Source{{12, 34}}, 2u),
|
||||
Expr(3u),
|
||||
Expr(Source{{56, 78}}, 2u),
|
||||
}),
|
||||
DefaultCase()));
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: duplicate switch case '2'\n"
|
||||
"12:34 note: previous case declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
NonUniqueCaseSelectorValueSint_Fail) {
|
||||
// var a : i32 = 2;
|
||||
// switch (a) {
|
||||
// case -10: {}
|
||||
// case 0,1,2,-10: {}
|
||||
// default: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
Case(Expr(Source{{12, 34}}, -10)),
|
||||
Case({
|
||||
Expr(0),
|
||||
Expr(1),
|
||||
Expr(2),
|
||||
Expr(Source{{56, 78}}, -10),
|
||||
}),
|
||||
DefaultCase()));
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: duplicate switch case '-10'\n"
|
||||
"12:34 note: previous case declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest,
|
||||
LastClauseLastStatementIsFallthrough_Fail) {
|
||||
// var a : i32 = 2;
|
||||
// switch (a) {
|
||||
// default: { fallthrough; }
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
auto* fallthrough = create<ast::FallthroughStatement>(Source{{12, 34}});
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
DefaultCase(Block(fallthrough))));
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: a fallthrough statement must not be used in the last "
|
||||
"switch case");
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, SwitchCase_Pass) {
|
||||
// var a : i32 = 2;
|
||||
// switch (a) {
|
||||
// default: {}
|
||||
// case 5: {}
|
||||
// }
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", //
|
||||
DefaultCase(Source{{12, 34}}), //
|
||||
Case(Expr(5))));
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverControlBlockValidationTest, SwitchCaseAlias_Pass) {
|
||||
// type MyInt = u32;
|
||||
// var v: MyInt;
|
||||
// switch(v){
|
||||
// default: {}
|
||||
// }
|
||||
|
||||
auto* my_int = Alias("MyInt", ty.u32());
|
||||
auto* var = Var("a", ty.Of(my_int), Expr(2u));
|
||||
auto* block = Block(Decl(var), //
|
||||
Switch("a", DefaultCase(Source{{12, 34}})));
|
||||
|
||||
WrapInFunction(block);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tint
|
||||
736
src/tint/resolver/dependency_graph.cc
Normal file
736
src/tint/resolver/dependency_graph.cc
Normal file
@@ -0,0 +1,736 @@
|
||||
// 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/tint/resolver/dependency_graph.h"
|
||||
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "src/tint/ast/continue_statement.h"
|
||||
#include "src/tint/ast/discard_statement.h"
|
||||
#include "src/tint/ast/fallthrough_statement.h"
|
||||
#include "src/tint/ast/traverse_expressions.h"
|
||||
#include "src/tint/scope_stack.h"
|
||||
#include "src/tint/sem/builtin.h"
|
||||
#include "src/tint/utils/defer.h"
|
||||
#include "src/tint/utils/map.h"
|
||||
#include "src/tint/utils/scoped_assignment.h"
|
||||
#include "src/tint/utils/unique_vector.h"
|
||||
|
||||
#define TINT_DUMP_DEPENDENCY_GRAPH 0
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
// Forward declaration
|
||||
struct Global;
|
||||
|
||||
/// Dependency describes how one global depends on another global
|
||||
struct DependencyInfo {
|
||||
/// The source of the symbol that forms the dependency
|
||||
Source source;
|
||||
/// A string describing how the dependency is referenced. e.g. 'calls'
|
||||
const char* action = nullptr;
|
||||
};
|
||||
|
||||
/// DependencyEdge describes the two Globals used to define a dependency
|
||||
/// relationship.
|
||||
struct DependencyEdge {
|
||||
/// The Global that depends on #to
|
||||
const Global* from;
|
||||
/// The Global that is depended on by #from
|
||||
const Global* to;
|
||||
};
|
||||
|
||||
/// DependencyEdgeCmp implements the contracts of std::equal_to<DependencyEdge>
|
||||
/// and std::hash<DependencyEdge>.
|
||||
struct DependencyEdgeCmp {
|
||||
/// Equality operator
|
||||
bool operator()(const DependencyEdge& lhs, const DependencyEdge& rhs) const {
|
||||
return lhs.from == rhs.from && lhs.to == rhs.to;
|
||||
}
|
||||
/// Hashing operator
|
||||
inline std::size_t operator()(const DependencyEdge& d) const {
|
||||
return utils::Hash(d.from, d.to);
|
||||
}
|
||||
};
|
||||
|
||||
/// A map of DependencyEdge to DependencyInfo
|
||||
using DependencyEdges = std::unordered_map<DependencyEdge,
|
||||
DependencyInfo,
|
||||
DependencyEdgeCmp,
|
||||
DependencyEdgeCmp>;
|
||||
|
||||
/// Global describes a module-scope variable, type or function.
|
||||
struct Global {
|
||||
explicit Global(const ast::Node* n) : node(n) {}
|
||||
|
||||
/// The declaration ast::Node
|
||||
const ast::Node* node;
|
||||
/// A list of dependencies that this global depends on
|
||||
std::vector<Global*> deps;
|
||||
};
|
||||
|
||||
/// A map of global name to Global
|
||||
using GlobalMap = std::unordered_map<Symbol, Global*>;
|
||||
|
||||
/// Raises an ICE that a global ast::Node type was not handled by this system.
|
||||
void UnhandledNode(diag::List& diagnostics, const ast::Node* node) {
|
||||
TINT_ICE(Resolver, diagnostics)
|
||||
<< "unhandled node type: " << node->TypeInfo().name;
|
||||
}
|
||||
|
||||
/// Raises an error diagnostic with the given message and source.
|
||||
void AddError(diag::List& diagnostics,
|
||||
const std::string& msg,
|
||||
const Source& source) {
|
||||
diagnostics.add_error(diag::System::Resolver, msg, source);
|
||||
}
|
||||
|
||||
/// Raises a note diagnostic with the given message and source.
|
||||
void AddNote(diag::List& diagnostics,
|
||||
const std::string& msg,
|
||||
const Source& source) {
|
||||
diagnostics.add_note(diag::System::Resolver, msg, source);
|
||||
}
|
||||
|
||||
/// DependencyScanner is used to traverse a module to build the list of
|
||||
/// global-to-global dependencies.
|
||||
class DependencyScanner {
|
||||
public:
|
||||
/// Constructor
|
||||
/// @param syms the program symbol table
|
||||
/// @param globals_by_name map of global symbol to Global pointer
|
||||
/// @param diagnostics diagnostic messages, appended with any errors found
|
||||
/// @param graph the dependency graph to populate with resolved symbols
|
||||
/// @param edges the map of globals-to-global dependency edges, which will
|
||||
/// be populated by calls to Scan()
|
||||
DependencyScanner(const SymbolTable& syms,
|
||||
const GlobalMap& globals_by_name,
|
||||
diag::List& diagnostics,
|
||||
DependencyGraph& graph,
|
||||
DependencyEdges& edges)
|
||||
: symbols_(syms),
|
||||
globals_(globals_by_name),
|
||||
diagnostics_(diagnostics),
|
||||
graph_(graph),
|
||||
dependency_edges_(edges) {
|
||||
// Register all the globals at global-scope
|
||||
for (auto it : globals_by_name) {
|
||||
scope_stack_.Set(it.first, it.second->node);
|
||||
}
|
||||
}
|
||||
|
||||
/// Walks the global declarations, resolving symbols, and determining the
|
||||
/// dependencies of each global.
|
||||
void Scan(Global* global) {
|
||||
TINT_SCOPED_ASSIGNMENT(current_global_, global);
|
||||
Switch(
|
||||
global->node,
|
||||
[&](const ast::Struct* str) {
|
||||
Declare(str->name, str);
|
||||
for (auto* member : str->members) {
|
||||
TraverseType(member->type);
|
||||
}
|
||||
},
|
||||
[&](const ast::Alias* alias) {
|
||||
Declare(alias->name, alias);
|
||||
TraverseType(alias->type);
|
||||
},
|
||||
[&](const ast::Function* func) {
|
||||
Declare(func->symbol, func);
|
||||
TraverseAttributes(func->attributes);
|
||||
TraverseFunction(func);
|
||||
},
|
||||
[&](const ast::Variable* var) {
|
||||
Declare(var->symbol, var);
|
||||
TraverseType(var->type);
|
||||
if (var->constructor) {
|
||||
TraverseExpression(var->constructor);
|
||||
}
|
||||
},
|
||||
[&](Default) { UnhandledNode(diagnostics_, global->node); });
|
||||
}
|
||||
|
||||
private:
|
||||
/// Traverses the function, performing symbol resolution and determining
|
||||
/// global dependencies.
|
||||
void TraverseFunction(const ast::Function* func) {
|
||||
// Perform symbol resolution on all the parameter types before registering
|
||||
// the parameters themselves. This allows the case of declaring a parameter
|
||||
// with the same identifier as its type.
|
||||
for (auto* param : func->params) {
|
||||
TraverseType(param->type);
|
||||
}
|
||||
// Resolve the return type
|
||||
TraverseType(func->return_type);
|
||||
|
||||
// Push the scope stack for the parameters and function body.
|
||||
scope_stack_.Push();
|
||||
TINT_DEFER(scope_stack_.Pop());
|
||||
|
||||
for (auto* param : func->params) {
|
||||
if (auto* shadows = scope_stack_.Get(param->symbol)) {
|
||||
graph_.shadows.emplace(param, shadows);
|
||||
}
|
||||
Declare(param->symbol, param);
|
||||
}
|
||||
if (func->body) {
|
||||
TraverseStatements(func->body->statements);
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses the statements, performing symbol resolution and determining
|
||||
/// global dependencies.
|
||||
void TraverseStatements(const ast::StatementList& stmts) {
|
||||
for (auto* s : stmts) {
|
||||
TraverseStatement(s);
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses the statement, performing symbol resolution and determining
|
||||
/// global dependencies.
|
||||
void TraverseStatement(const ast::Statement* stmt) {
|
||||
if (!stmt) {
|
||||
return;
|
||||
}
|
||||
Switch(
|
||||
stmt, //
|
||||
[&](const ast::AssignmentStatement* a) {
|
||||
TraverseExpression(a->lhs);
|
||||
TraverseExpression(a->rhs);
|
||||
},
|
||||
[&](const ast::BlockStatement* b) {
|
||||
scope_stack_.Push();
|
||||
TINT_DEFER(scope_stack_.Pop());
|
||||
TraverseStatements(b->statements);
|
||||
},
|
||||
[&](const ast::CallStatement* r) { //
|
||||
TraverseExpression(r->expr);
|
||||
},
|
||||
[&](const ast::ForLoopStatement* l) {
|
||||
scope_stack_.Push();
|
||||
TINT_DEFER(scope_stack_.Pop());
|
||||
TraverseStatement(l->initializer);
|
||||
TraverseExpression(l->condition);
|
||||
TraverseStatement(l->continuing);
|
||||
TraverseStatement(l->body);
|
||||
},
|
||||
[&](const ast::LoopStatement* l) {
|
||||
scope_stack_.Push();
|
||||
TINT_DEFER(scope_stack_.Pop());
|
||||
TraverseStatements(l->body->statements);
|
||||
TraverseStatement(l->continuing);
|
||||
},
|
||||
[&](const ast::IfStatement* i) {
|
||||
TraverseExpression(i->condition);
|
||||
TraverseStatement(i->body);
|
||||
for (auto* e : i->else_statements) {
|
||||
TraverseExpression(e->condition);
|
||||
TraverseStatement(e->body);
|
||||
}
|
||||
},
|
||||
[&](const ast::ReturnStatement* r) { //
|
||||
TraverseExpression(r->value);
|
||||
},
|
||||
[&](const ast::SwitchStatement* s) {
|
||||
TraverseExpression(s->condition);
|
||||
for (auto* c : s->body) {
|
||||
for (auto* sel : c->selectors) {
|
||||
TraverseExpression(sel);
|
||||
}
|
||||
TraverseStatement(c->body);
|
||||
}
|
||||
},
|
||||
[&](const ast::VariableDeclStatement* v) {
|
||||
if (auto* shadows = scope_stack_.Get(v->variable->symbol)) {
|
||||
graph_.shadows.emplace(v->variable, shadows);
|
||||
}
|
||||
TraverseType(v->variable->type);
|
||||
TraverseExpression(v->variable->constructor);
|
||||
Declare(v->variable->symbol, v->variable);
|
||||
},
|
||||
[&](Default) {
|
||||
if (!stmt->IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
|
||||
ast::DiscardStatement,
|
||||
ast::FallthroughStatement>()) {
|
||||
UnhandledNode(diagnostics_, stmt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds the symbol definition to the current scope, raising an error if two
|
||||
/// symbols collide within the same scope.
|
||||
void Declare(Symbol symbol, const ast::Node* node) {
|
||||
auto* old = scope_stack_.Set(symbol, node);
|
||||
if (old != nullptr && node != old) {
|
||||
auto name = symbols_.NameFor(symbol);
|
||||
AddError(diagnostics_, "redeclaration of '" + name + "'", node->source);
|
||||
AddNote(diagnostics_, "'" + name + "' previously declared here",
|
||||
old->source);
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses the expression, performing symbol resolution and determining
|
||||
/// global dependencies.
|
||||
void TraverseExpression(const ast::Expression* root) {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
ast::TraverseExpressions(
|
||||
root, diagnostics_, [&](const ast::Expression* expr) {
|
||||
Switch(
|
||||
expr,
|
||||
[&](const ast::IdentifierExpression* ident) {
|
||||
AddDependency(ident, ident->symbol, "identifier", "references");
|
||||
},
|
||||
[&](const ast::CallExpression* call) {
|
||||
if (call->target.name) {
|
||||
AddDependency(call->target.name, call->target.name->symbol,
|
||||
"function", "calls");
|
||||
}
|
||||
if (call->target.type) {
|
||||
TraverseType(call->target.type);
|
||||
}
|
||||
},
|
||||
[&](const ast::BitcastExpression* cast) {
|
||||
TraverseType(cast->type);
|
||||
});
|
||||
return ast::TraverseAction::Descend;
|
||||
});
|
||||
}
|
||||
|
||||
/// Traverses the type node, performing symbol resolution and determining
|
||||
/// global dependencies.
|
||||
void TraverseType(const ast::Type* ty) {
|
||||
if (!ty) {
|
||||
return;
|
||||
}
|
||||
Switch(
|
||||
ty, //
|
||||
[&](const ast::Array* arr) {
|
||||
TraverseType(arr->type); //
|
||||
TraverseExpression(arr->count);
|
||||
},
|
||||
[&](const ast::Atomic* atomic) { //
|
||||
TraverseType(atomic->type);
|
||||
},
|
||||
[&](const ast::Matrix* mat) { //
|
||||
TraverseType(mat->type);
|
||||
},
|
||||
[&](const ast::Pointer* ptr) { //
|
||||
TraverseType(ptr->type);
|
||||
},
|
||||
[&](const ast::TypeName* tn) { //
|
||||
AddDependency(tn, tn->name, "type", "references");
|
||||
},
|
||||
[&](const ast::Vector* vec) { //
|
||||
TraverseType(vec->type);
|
||||
},
|
||||
[&](const ast::SampledTexture* tex) { //
|
||||
TraverseType(tex->type);
|
||||
},
|
||||
[&](const ast::MultisampledTexture* tex) { //
|
||||
TraverseType(tex->type);
|
||||
},
|
||||
[&](Default) {
|
||||
if (!ty->IsAnyOf<ast::Void, ast::Bool, ast::I32, ast::U32, ast::F32,
|
||||
ast::DepthTexture, ast::DepthMultisampledTexture,
|
||||
ast::StorageTexture, ast::ExternalTexture,
|
||||
ast::Sampler>()) {
|
||||
UnhandledNode(diagnostics_, ty);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Traverses the attribute list, performing symbol resolution and
|
||||
/// determining global dependencies.
|
||||
void TraverseAttributes(const ast::AttributeList& attrs) {
|
||||
for (auto* attr : attrs) {
|
||||
TraverseAttribute(attr);
|
||||
}
|
||||
}
|
||||
|
||||
/// Traverses the attribute, performing symbol resolution and determining
|
||||
/// global dependencies.
|
||||
void TraverseAttribute(const ast::Attribute* attr) {
|
||||
if (auto* wg = attr->As<ast::WorkgroupAttribute>()) {
|
||||
TraverseExpression(wg->x);
|
||||
TraverseExpression(wg->y);
|
||||
TraverseExpression(wg->z);
|
||||
return;
|
||||
}
|
||||
if (attr->IsAnyOf<
|
||||
ast::BindingAttribute, ast::BuiltinAttribute, ast::GroupAttribute,
|
||||
ast::IdAttribute, ast::InternalAttribute, ast::InterpolateAttribute,
|
||||
ast::InvariantAttribute, ast::LocationAttribute,
|
||||
ast::StageAttribute, ast::StrideAttribute,
|
||||
ast::StructBlockAttribute, ast::StructMemberAlignAttribute,
|
||||
ast::StructMemberOffsetAttribute,
|
||||
ast::StructMemberSizeAttribute>()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UnhandledNode(diagnostics_, attr);
|
||||
}
|
||||
|
||||
/// Adds the dependency from `from` to `to`, erroring if `to` cannot be
|
||||
/// resolved.
|
||||
void AddDependency(const ast::Node* from,
|
||||
Symbol to,
|
||||
const char* use,
|
||||
const char* action) {
|
||||
auto* resolved = scope_stack_.Get(to);
|
||||
if (!resolved) {
|
||||
if (!IsBuiltin(to)) {
|
||||
UnknownSymbol(to, from->source, use);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* global = utils::Lookup(globals_, to);
|
||||
global && global->node == resolved) {
|
||||
if (dependency_edges_
|
||||
.emplace(DependencyEdge{current_global_, global},
|
||||
DependencyInfo{from->source, action})
|
||||
.second) {
|
||||
current_global_->deps.emplace_back(global);
|
||||
}
|
||||
}
|
||||
|
||||
graph_.resolved_symbols.emplace(from, resolved);
|
||||
}
|
||||
|
||||
/// @returns true if `name` is the name of a builtin function
|
||||
bool IsBuiltin(Symbol name) const {
|
||||
return sem::ParseBuiltinType(symbols_.NameFor(name)) !=
|
||||
sem::BuiltinType::kNone;
|
||||
}
|
||||
|
||||
/// Appends an error to the diagnostics that the given symbol cannot be
|
||||
/// resolved.
|
||||
void UnknownSymbol(Symbol name, Source source, const char* use) {
|
||||
AddError(
|
||||
diagnostics_,
|
||||
"unknown " + std::string(use) + ": '" + symbols_.NameFor(name) + "'",
|
||||
source);
|
||||
}
|
||||
|
||||
using VariableMap = std::unordered_map<Symbol, const ast::Variable*>;
|
||||
const SymbolTable& symbols_;
|
||||
const GlobalMap& globals_;
|
||||
diag::List& diagnostics_;
|
||||
DependencyGraph& graph_;
|
||||
DependencyEdges& dependency_edges_;
|
||||
|
||||
ScopeStack<const ast::Node*> scope_stack_;
|
||||
Global* current_global_ = nullptr;
|
||||
};
|
||||
|
||||
/// The global dependency analysis system
|
||||
struct DependencyAnalysis {
|
||||
public:
|
||||
/// Constructor
|
||||
DependencyAnalysis(const SymbolTable& symbols,
|
||||
diag::List& diagnostics,
|
||||
DependencyGraph& graph)
|
||||
: symbols_(symbols), diagnostics_(diagnostics), graph_(graph) {}
|
||||
|
||||
/// Performs global dependency analysis on the module, emitting any errors to
|
||||
/// #diagnostics.
|
||||
/// @returns true if analysis found no errors, otherwise false.
|
||||
bool Run(const ast::Module& module) {
|
||||
// Collect all the named globals from the AST module
|
||||
GatherGlobals(module);
|
||||
|
||||
// Traverse the named globals to build the dependency graph
|
||||
DetermineDependencies();
|
||||
|
||||
// Sort the globals into dependency order
|
||||
SortGlobals();
|
||||
|
||||
// Dump the dependency graph if TINT_DUMP_DEPENDENCY_GRAPH is non-zero
|
||||
DumpDependencyGraph();
|
||||
|
||||
graph_.ordered_globals = std::move(sorted_);
|
||||
|
||||
return !diagnostics_.contains_errors();
|
||||
}
|
||||
|
||||
private:
|
||||
/// @param node the ast::Node of the global declaration
|
||||
/// @returns the symbol of the global declaration node
|
||||
/// @note will raise an ICE if the node is not a type, function or variable
|
||||
/// declaration
|
||||
Symbol SymbolOf(const ast::Node* node) const {
|
||||
return Switch(
|
||||
node, //
|
||||
[&](const ast::TypeDecl* td) { return td->name; },
|
||||
[&](const ast::Function* func) { return func->symbol; },
|
||||
[&](const ast::Variable* var) { return var->symbol; },
|
||||
[&](Default) {
|
||||
UnhandledNode(diagnostics_, node);
|
||||
return Symbol{};
|
||||
});
|
||||
}
|
||||
|
||||
/// @param node the ast::Node of the global declaration
|
||||
/// @returns the name of the global declaration node
|
||||
/// @note will raise an ICE if the node is not a type, function or variable
|
||||
/// declaration
|
||||
std::string NameOf(const ast::Node* node) const {
|
||||
return symbols_.NameFor(SymbolOf(node));
|
||||
}
|
||||
|
||||
/// @param node the ast::Node of the global declaration
|
||||
/// @returns a string representation of the global declaration kind
|
||||
/// @note will raise an ICE if the node is not a type, function or variable
|
||||
/// declaration
|
||||
std::string KindOf(const ast::Node* node) {
|
||||
return Switch(
|
||||
node, //
|
||||
[&](const ast::Struct*) { return "struct"; },
|
||||
[&](const ast::Alias*) { return "alias"; },
|
||||
[&](const ast::Function*) { return "function"; },
|
||||
[&](const ast::Variable* var) { return var->is_const ? "let" : "var"; },
|
||||
[&](Default) {
|
||||
UnhandledNode(diagnostics_, node);
|
||||
return "<error>";
|
||||
});
|
||||
}
|
||||
|
||||
/// Traverses `module`, collecting all the global declarations and populating
|
||||
/// the #globals and #declaration_order fields.
|
||||
void GatherGlobals(const ast::Module& module) {
|
||||
for (auto* node : module.GlobalDeclarations()) {
|
||||
auto* global = allocator_.Create(node);
|
||||
globals_.emplace(SymbolOf(node), global);
|
||||
declaration_order_.emplace_back(global);
|
||||
}
|
||||
}
|
||||
|
||||
/// Walks the global declarations, determining the dependencies of each global
|
||||
/// and adding these to each global's Global::deps field.
|
||||
void DetermineDependencies() {
|
||||
DependencyScanner scanner(symbols_, globals_, diagnostics_, graph_,
|
||||
dependency_edges_);
|
||||
for (auto* global : declaration_order_) {
|
||||
scanner.Scan(global);
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a depth-first traversal of `root`'s dependencies, calling `enter`
|
||||
/// as the function decends into each dependency and `exit` when bubbling back
|
||||
/// up towards the root.
|
||||
/// @param enter is a function with the signature: `bool(Global*)`. The
|
||||
/// `enter` function returns true if TraverseDependencies() should traverse
|
||||
/// the dependency, otherwise it will be skipped.
|
||||
/// @param exit is a function with the signature: `void(Global*)`. The `exit`
|
||||
/// function is only called if the corresponding `enter` call returned true.
|
||||
template <typename ENTER, typename EXIT>
|
||||
void TraverseDependencies(const Global* root, ENTER&& enter, EXIT&& exit) {
|
||||
// Entry is a single entry in the traversal stack. Entry points to a
|
||||
// dep_idx'th dependency of Entry::global.
|
||||
struct Entry {
|
||||
const Global* global; // The parent global
|
||||
size_t dep_idx; // The dependency index in `global->deps`
|
||||
};
|
||||
|
||||
if (!enter(root)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Entry> stack{Entry{root, 0}};
|
||||
while (true) {
|
||||
auto& entry = stack.back();
|
||||
// Have we exhausted the dependencies of entry.global?
|
||||
if (entry.dep_idx < entry.global->deps.size()) {
|
||||
// No, there's more dependencies to traverse.
|
||||
auto& dep = entry.global->deps[entry.dep_idx];
|
||||
// Does the caller want to enter this dependency?
|
||||
if (enter(dep)) { // Yes.
|
||||
stack.push_back(Entry{dep, 0}); // Enter the dependency.
|
||||
} else {
|
||||
entry.dep_idx++; // No. Skip this node.
|
||||
}
|
||||
} else {
|
||||
// Yes. Time to back up.
|
||||
// Exit this global, pop the stack, and if there's another parent node,
|
||||
// increment its dependency index, and loop again.
|
||||
exit(entry.global);
|
||||
stack.pop_back();
|
||||
if (stack.empty()) {
|
||||
return; // All done.
|
||||
}
|
||||
stack.back().dep_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SortGlobals sorts the globals into dependency order, erroring if cyclic
|
||||
/// dependencies are found. The sorted dependencies are assigned to #sorted.
|
||||
void SortGlobals() {
|
||||
if (diagnostics_.contains_errors()) {
|
||||
return; // This code assumes there are no undeclared identifiers.
|
||||
}
|
||||
|
||||
std::unordered_set<const Global*> visited;
|
||||
for (auto* global : declaration_order_) {
|
||||
utils::UniqueVector<const Global*> stack;
|
||||
TraverseDependencies(
|
||||
global,
|
||||
[&](const Global* g) { // Enter
|
||||
if (!stack.add(g)) {
|
||||
CyclicDependencyFound(g, stack);
|
||||
return false;
|
||||
}
|
||||
if (sorted_.contains(g->node)) {
|
||||
// Visited this global already.
|
||||
// stack was pushed, but exit() will not be called when we return
|
||||
// false, so pop here.
|
||||
stack.pop_back();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
[&](const Global* g) { // Exit. Only called if Enter returned true.
|
||||
sorted_.add(g->node);
|
||||
stack.pop_back();
|
||||
});
|
||||
|
||||
sorted_.add(global->node);
|
||||
|
||||
if (!stack.empty()) {
|
||||
// Each stack.push() must have a corresponding stack.pop_back().
|
||||
TINT_ICE(Resolver, diagnostics_)
|
||||
<< "stack not empty after returning from TraverseDependencies()";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// DepInfoFor() looks up the global dependency information for the dependency
|
||||
/// of global `from` depending on `to`.
|
||||
/// @note will raise an ICE if the edge is not found.
|
||||
DependencyInfo DepInfoFor(const Global* from, const Global* to) const {
|
||||
auto it = dependency_edges_.find(DependencyEdge{from, to});
|
||||
if (it != dependency_edges_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
TINT_ICE(Resolver, diagnostics_)
|
||||
<< "failed to find dependency info for edge: '" << NameOf(from->node)
|
||||
<< "' -> '" << NameOf(to->node) << "'";
|
||||
return {};
|
||||
}
|
||||
|
||||
/// CyclicDependencyFound() emits an error diagnostic for a cyclic dependency.
|
||||
/// @param root is the global that starts the cyclic dependency, which must be
|
||||
/// found in `stack`.
|
||||
/// @param stack is the global dependency stack that contains a loop.
|
||||
void CyclicDependencyFound(const Global* root,
|
||||
const std::vector<const Global*>& stack) {
|
||||
std::stringstream msg;
|
||||
msg << "cyclic dependency found: ";
|
||||
constexpr size_t kLoopNotStarted = ~0u;
|
||||
size_t loop_start = kLoopNotStarted;
|
||||
for (size_t i = 0; i < stack.size(); i++) {
|
||||
auto* e = stack[i];
|
||||
if (loop_start == kLoopNotStarted && e == root) {
|
||||
loop_start = i;
|
||||
}
|
||||
if (loop_start != kLoopNotStarted) {
|
||||
msg << "'" << NameOf(e->node) << "' -> ";
|
||||
}
|
||||
}
|
||||
msg << "'" << NameOf(root->node) << "'";
|
||||
AddError(diagnostics_, msg.str(), root->node->source);
|
||||
for (size_t i = loop_start; i < stack.size(); i++) {
|
||||
auto* from = stack[i];
|
||||
auto* to = (i + 1 < stack.size()) ? stack[i + 1] : stack[loop_start];
|
||||
auto info = DepInfoFor(from, to);
|
||||
AddNote(diagnostics_,
|
||||
KindOf(from->node) + " '" + NameOf(from->node) + "' " +
|
||||
info.action + " " + KindOf(to->node) + " '" +
|
||||
NameOf(to->node) + "' here",
|
||||
info.source);
|
||||
}
|
||||
}
|
||||
|
||||
void DumpDependencyGraph() {
|
||||
#if TINT_DUMP_DEPENDENCY_GRAPH == 0
|
||||
if ((true)) {
|
||||
return;
|
||||
}
|
||||
#endif // TINT_DUMP_DEPENDENCY_GRAPH
|
||||
printf("=========================\n");
|
||||
printf("------ declaration ------ \n");
|
||||
for (auto* global : declaration_order_) {
|
||||
printf("%s\n", NameOf(global->node).c_str());
|
||||
}
|
||||
printf("------ dependencies ------ \n");
|
||||
for (auto* node : sorted_) {
|
||||
auto symbol = SymbolOf(node);
|
||||
auto* global = globals_.at(symbol);
|
||||
printf("%s depends on:\n", symbols_.NameFor(symbol).c_str());
|
||||
for (auto* dep : global->deps) {
|
||||
printf(" %s\n", NameOf(dep->node).c_str());
|
||||
}
|
||||
}
|
||||
printf("=========================\n");
|
||||
}
|
||||
|
||||
/// Program symbols
|
||||
const SymbolTable& symbols_;
|
||||
|
||||
/// Program diagnostics
|
||||
diag::List& diagnostics_;
|
||||
|
||||
/// The resulting dependency graph
|
||||
DependencyGraph& graph_;
|
||||
|
||||
/// Allocator of Globals
|
||||
BlockAllocator<Global> allocator_;
|
||||
|
||||
/// Global map, keyed by name. Populated by GatherGlobals().
|
||||
GlobalMap globals_;
|
||||
|
||||
/// Map of DependencyEdge to DependencyInfo. Populated by
|
||||
/// DetermineDependencies().
|
||||
DependencyEdges dependency_edges_;
|
||||
|
||||
/// Globals in declaration order. Populated by GatherGlobals().
|
||||
std::vector<Global*> declaration_order_;
|
||||
|
||||
/// Globals in sorted dependency order. Populated by SortGlobals().
|
||||
utils::UniqueVector<const ast::Node*> sorted_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
DependencyGraph::DependencyGraph() = default;
|
||||
DependencyGraph::DependencyGraph(DependencyGraph&&) = default;
|
||||
DependencyGraph::~DependencyGraph() = default;
|
||||
|
||||
bool DependencyGraph::Build(const ast::Module& module,
|
||||
const SymbolTable& symbols,
|
||||
diag::List& diagnostics,
|
||||
DependencyGraph& output) {
|
||||
DependencyAnalysis da{symbols, diagnostics, output};
|
||||
return da.Run(module);
|
||||
}
|
||||
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
66
src/tint/resolver/dependency_graph.h
Normal file
66
src/tint/resolver/dependency_graph.h
Normal file
@@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
|
||||
#ifndef SRC_TINT_RESOLVER_DEPENDENCY_GRAPH_H_
|
||||
#define SRC_TINT_RESOLVER_DEPENDENCY_GRAPH_H_
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "src/tint/ast/module.h"
|
||||
#include "src/tint/diagnostic/diagnostic.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
|
||||
/// DependencyGraph holds information about module-scope declaration dependency
|
||||
/// analysis and symbol resolutions.
|
||||
struct DependencyGraph {
|
||||
/// Constructor
|
||||
DependencyGraph();
|
||||
/// Move-constructor
|
||||
DependencyGraph(DependencyGraph&&);
|
||||
/// Destructor
|
||||
~DependencyGraph();
|
||||
|
||||
/// Build() performs symbol resolution and dependency analysis on `module`,
|
||||
/// populating `output` with the resulting dependency graph.
|
||||
/// @param module the AST module to analyse
|
||||
/// @param symbols the symbol table
|
||||
/// @param diagnostics the diagnostic list to populate with errors / warnings
|
||||
/// @param output the resulting DependencyGraph
|
||||
/// @returns true on success, false on error
|
||||
static bool Build(const ast::Module& module,
|
||||
const SymbolTable& symbols,
|
||||
diag::List& diagnostics,
|
||||
DependencyGraph& output);
|
||||
|
||||
/// All globals in dependency-sorted order.
|
||||
std::vector<const ast::Node*> ordered_globals;
|
||||
|
||||
/// Map of ast::IdentifierExpression or ast::TypeName to a type, function, or
|
||||
/// variable that declares the symbol.
|
||||
std::unordered_map<const ast::Node*, const ast::Node*> resolved_symbols;
|
||||
|
||||
/// Map of ast::Variable to a type, function, or variable that is shadowed by
|
||||
/// the variable key. A declaration (X) shadows another (Y) if X and Y use
|
||||
/// the same symbol, and X is declared in a sub-scope of the scope that
|
||||
/// declares Y.
|
||||
std::unordered_map<const ast::Variable*, const ast::Node*> shadows;
|
||||
};
|
||||
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_TINT_RESOLVER_DEPENDENCY_GRAPH_H_
|
||||
1342
src/tint/resolver/dependency_graph_test.cc
Normal file
1342
src/tint/resolver/dependency_graph_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
804
src/tint/resolver/entry_point_validation_test.cc
Normal file
804
src/tint/resolver/entry_point_validation_test.cc
Normal file
@@ -0,0 +1,804 @@
|
||||
// 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/tint/ast/builtin_attribute.h"
|
||||
#include "src/tint/ast/location_attribute.h"
|
||||
#include "src/tint/ast/return_statement.h"
|
||||
#include "src/tint/ast/stage_attribute.h"
|
||||
#include "src/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.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 <typename T>
|
||||
using alias = builder::alias<T>;
|
||||
using f32 = builder::f32;
|
||||
using i32 = builder::i32;
|
||||
using u32 = builder::u32;
|
||||
|
||||
class ResolverEntryPointValidationTest : public TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Location) {
|
||||
// @stage(fragment)
|
||||
// fn main() -> @location(0) f32 { return 1.0; }
|
||||
Func(Source{{12, 34}}, "main", {}, ty.f32(), {Return(1.0f)},
|
||||
{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Builtin) {
|
||||
// @stage(vertex)
|
||||
// fn main() -> @builtin(position) vec4<f32> { return vec4<f32>(); }
|
||||
Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
|
||||
{Return(Construct(ty.vec4<f32>()))},
|
||||
{Stage(ast::PipelineStage::kVertex)},
|
||||
{Builtin(ast::Builtin::kPosition)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Missing) {
|
||||
// @stage(vertex)
|
||||
// fn main() -> f32 {
|
||||
// return 1.0;
|
||||
// }
|
||||
Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
|
||||
{Return(Construct(ty.vec4<f32>()))},
|
||||
{Stage(ast::PipelineStage::kVertex)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: missing entry point IO attribute on return type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, ReturnTypeAttribute_Multiple) {
|
||||
// @stage(vertex)
|
||||
// fn main() -> @location(0) @builtin(position) vec4<f32> {
|
||||
// return vec4<f32>();
|
||||
// }
|
||||
Func(Source{{12, 34}}, "main", {}, ty.vec4<f32>(),
|
||||
{Return(Construct(ty.vec4<f32>()))},
|
||||
{Stage(ast::PipelineStage::kVertex)},
|
||||
{Location(Source{{13, 43}}, 0),
|
||||
Builtin(Source{{14, 52}}, ast::Builtin::kPosition)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
|
||||
13:43 note: previously consumed location(0))");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, 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(ResolverEntryPointValidationTest,
|
||||
ReturnType_Struct_MemberMultipleAttributes) {
|
||||
// struct Output {
|
||||
// @location(0) @builtin(frag_depth) a : f32;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main() -> Output {
|
||||
// return Output();
|
||||
// }
|
||||
auto* output = Structure(
|
||||
"Output",
|
||||
{Member("a", ty.f32(),
|
||||
{Location(Source{{13, 43}}, 0),
|
||||
Builtin(Source{{14, 52}}, ast::Builtin::kFragDepth)})});
|
||||
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: multiple entry point IO attributes
|
||||
13:43 note: previously consumed location(0)
|
||||
12:34 note: while analysing entry point 'main')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest,
|
||||
ReturnType_Struct_MemberMissingAttribute) {
|
||||
// struct Output {
|
||||
// @location(0) a : f32;
|
||||
// b : f32;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main() -> Output {
|
||||
// return Output();
|
||||
// }
|
||||
auto* output = Structure(
|
||||
"Output", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
|
||||
Member(Source{{14, 52}}, "b", ty.f32(), {})});
|
||||
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: missing entry point IO attribute
|
||||
12:34 note: while analysing entry point 'main')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, ReturnType_Struct_DuplicateBuiltins) {
|
||||
// struct Output {
|
||||
// @builtin(frag_depth) a : f32;
|
||||
// @builtin(frag_depth) b : f32;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main() -> Output {
|
||||
// return Output();
|
||||
// }
|
||||
auto* output = Structure(
|
||||
"Output", {Member("a", ty.f32(), {Builtin(ast::Builtin::kFragDepth)}),
|
||||
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_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: builtin(frag_depth) 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) {}
|
||||
auto* param = Param("param", ty.f32(), {Location(0)});
|
||||
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Missing) {
|
||||
// @stage(fragment)
|
||||
// fn main(param : f32) {}
|
||||
auto* param = Param(Source{{13, 43}}, "param", ty.vec4<f32>());
|
||||
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"13:43 error: missing entry point IO attribute on parameter");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, ParameterAttribute_Multiple) {
|
||||
// @stage(fragment)
|
||||
// fn main(@location(0) @builtin(sample_index) param : u32) {}
|
||||
auto* param = Param("param", ty.u32(),
|
||||
{Location(Source{{13, 43}}, 0),
|
||||
Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)});
|
||||
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(14:52 error: multiple entry point IO attributes
|
||||
13:43 note: previously consumed location(0))");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_Valid) {
|
||||
// struct Input {
|
||||
// @location(0) a : f32;
|
||||
// @builtin(sample_index) b : u32;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main(param : Input) {}
|
||||
auto* input = Structure(
|
||||
"Input", {Member("a", ty.f32(), {Location(0)}),
|
||||
Member("b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
|
||||
auto* param = Param("param", ty.Of(input));
|
||||
Func(Source{{12, 34}}, "main", {param}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest,
|
||||
Parameter_Struct_MemberMultipleAttributes) {
|
||||
// struct Input {
|
||||
// @location(0) @builtin(sample_index) a : u32;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main(param : Input) {}
|
||||
auto* input = Structure(
|
||||
"Input",
|
||||
{Member("a", ty.u32(),
|
||||
{Location(Source{{13, 43}}, 0),
|
||||
Builtin(Source{{14, 52}}, ast::Builtin::kSampleIndex)})});
|
||||
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: multiple entry point IO attributes
|
||||
13:43 note: previously consumed location(0)
|
||||
12:34 note: while analysing entry point 'main')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest,
|
||||
Parameter_Struct_MemberMissingAttribute) {
|
||||
// struct Input {
|
||||
// @location(0) a : f32;
|
||||
// b : f32;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main(param : Input) {}
|
||||
auto* input = Structure(
|
||||
"Input", {Member(Source{{13, 43}}, "a", ty.f32(), {Location(0)}),
|
||||
Member(Source{{14, 52}}, "b", ty.f32(), {})});
|
||||
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: missing entry point IO attribute
|
||||
12:34 note: while analysing entry point 'main')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, Parameter_DuplicateBuiltins) {
|
||||
// @stage(fragment)
|
||||
// fn main(@builtin(sample_index) param_a : u32,
|
||||
// @builtin(sample_index) param_b : u32) {}
|
||||
auto* param_a =
|
||||
Param("param_a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
|
||||
auto* param_b =
|
||||
Param("param_b", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)});
|
||||
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: builtin(sample_index) attribute appears multiple times as "
|
||||
"pipeline input");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, Parameter_Struct_DuplicateBuiltins) {
|
||||
// struct InputA {
|
||||
// @builtin(sample_index) a : u32;
|
||||
// };
|
||||
// struct InputB {
|
||||
// @builtin(sample_index) a : u32;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main(param_a : InputA, param_b : InputB) {}
|
||||
auto* input_a = Structure(
|
||||
"InputA", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
|
||||
auto* input_b = Structure(
|
||||
"InputB", {Member("a", ty.u32(), {Builtin(ast::Builtin::kSampleIndex)})});
|
||||
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: builtin(sample_index) attribute appears multiple times as pipeline input
|
||||
12:34 note: while analysing entry point 'main')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverEntryPointValidationTest, VertexShaderMustReturnPosition) {
|
||||
// @stage(vertex)
|
||||
// fn main() {}
|
||||
Func(Source{{12, 34}}, "main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kVertex)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: a vertex shader must include the 'position' builtin "
|
||||
"in its return type");
|
||||
}
|
||||
|
||||
namespace TypeValidationTests {
|
||||
struct Params {
|
||||
builder::ast_type_func_ptr create_ast_type;
|
||||
bool is_valid;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr Params ParamsFor(bool is_valid) {
|
||||
return Params{DataType<T>::AST, is_valid};
|
||||
}
|
||||
|
||||
using TypeValidationTest = resolver::ResolverTestWithParam<Params>;
|
||||
|
||||
static constexpr Params cases[] = {
|
||||
ParamsFor<f32>(true), //
|
||||
ParamsFor<i32>(true), //
|
||||
ParamsFor<u32>(true), //
|
||||
ParamsFor<bool>(false), //
|
||||
ParamsFor<vec2<f32>>(true), //
|
||||
ParamsFor<vec3<f32>>(true), //
|
||||
ParamsFor<vec4<f32>>(true), //
|
||||
ParamsFor<mat2x2<f32>>(false), //
|
||||
ParamsFor<mat3x3<f32>>(false), //
|
||||
ParamsFor<mat4x4<f32>>(false), //
|
||||
ParamsFor<alias<f32>>(true), //
|
||||
ParamsFor<alias<i32>>(true), //
|
||||
ParamsFor<alias<u32>>(true), //
|
||||
ParamsFor<alias<bool>>(false), //
|
||||
};
|
||||
|
||||
TEST_P(TypeValidationTest, BareInputs) {
|
||||
// @stage(fragment)
|
||||
// fn main(@location(0) @interpolate(flat) a : *) {}
|
||||
auto params = GetParam();
|
||||
auto* a = Param("a", params.create_ast_type(*this), {Location(0), Flat()});
|
||||
Func(Source{{12, 34}}, "main", {a}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
if (params.is_valid) {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
} else {
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(TypeValidationTest, StructInputs) {
|
||||
// struct Input {
|
||||
// @location(0) @interpolate(flat) a : *;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main(a : Input) {}
|
||||
auto params = GetParam();
|
||||
auto* input = Structure("Input", {Member("a", params.create_ast_type(*this),
|
||||
{Location(0), Flat()})});
|
||||
auto* a = Param("a", ty.Of(input), {});
|
||||
Func(Source{{12, 34}}, "main", {a}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
if (params.is_valid) {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
} else {
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(TypeValidationTest, BareOutputs) {
|
||||
// @stage(fragment)
|
||||
// fn main() -> @location(0) * {
|
||||
// return *();
|
||||
// }
|
||||
auto params = GetParam();
|
||||
Func(Source{{12, 34}}, "main", {}, params.create_ast_type(*this),
|
||||
{Return(Construct(params.create_ast_type(*this)))},
|
||||
{Stage(ast::PipelineStage::kFragment)}, {Location(0)});
|
||||
|
||||
if (params.is_valid) {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
} else {
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(TypeValidationTest, StructOutputs) {
|
||||
// struct Output {
|
||||
// @location(0) a : *;
|
||||
// };
|
||||
// @stage(fragment)
|
||||
// fn main() -> Output {
|
||||
// return Output();
|
||||
// }
|
||||
auto params = GetParam();
|
||||
auto* output = Structure(
|
||||
"Output", {Member("a", params.create_ast_type(*this), {Location(0)})});
|
||||
Func(Source{{12, 34}}, "main", {}, ty.Of(output),
|
||||
{Return(Construct(ty.Of(output)))},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
if (params.is_valid) {
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
} else {
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
}
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverEntryPointValidationTest,
|
||||
TypeValidationTest,
|
||||
testing::ValuesIn(cases));
|
||||
|
||||
} // namespace TypeValidationTests
|
||||
|
||||
namespace LocationAttributeTests {
|
||||
namespace {
|
||||
using LocationAttributeTests = ResolverTest;
|
||||
|
||||
TEST_F(LocationAttributeTests, Pass) {
|
||||
// @stage(fragment)
|
||||
// fn frag_main(@location(0) @interpolate(flat) a: i32) {}
|
||||
|
||||
auto* p = Param(Source{{12, 34}}, "a", ty.i32(), {Location(0), Flat()});
|
||||
Func("frag_main", {p}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(LocationAttributeTests, 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(LocationAttributeTests, 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(LocationAttributeTests, 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(LocationAttributeTests, BadType_Input_Struct_NestedStruct) {
|
||||
// struct Inner {
|
||||
// @location(0) b : f32;
|
||||
// };
|
||||
// struct Input {
|
||||
// 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))});
|
||||
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: nested structures cannot be used for entry point IO\n"
|
||||
"12:34 note: while analysing entry point 'main'");
|
||||
}
|
||||
|
||||
TEST_F(LocationAttributeTests, 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::StructBlockAttribute>()});
|
||||
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(LocationAttributeTests, 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::AttributeList{Location(Source{{12, 34}}, 0u)});
|
||||
auto* s = Structure("S", {m}, ast::AttributeList{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(LocationAttributeTests, 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::AttributeList{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(LocationAttributeTests, BadMemberType_Unused) {
|
||||
// struct S { @location(0) m: mat3x2<f32>; };
|
||||
|
||||
auto* m = Member(Source{{34, 56}}, "m", ty.mat3x2<f32>(),
|
||||
ast::AttributeList{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(LocationAttributeTests, 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(LocationAttributeTests, 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(LocationAttributeTests, ReturnType_Struct_NestedStruct) {
|
||||
// struct Inner {
|
||||
// @location(0) b : f32;
|
||||
// };
|
||||
// struct Output {
|
||||
// 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))});
|
||||
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(),
|
||||
"14:52 error: nested structures cannot be used for entry point IO\n"
|
||||
"12:34 note: while analysing entry point 'main'");
|
||||
}
|
||||
|
||||
TEST_F(LocationAttributeTests, 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::StructBlockAttribute>()});
|
||||
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(LocationAttributeTests, ComputeShaderLocation_Input) {
|
||||
Func("main", {}, ty.i32(), {Return(Expr(1))},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))},
|
||||
ast::AttributeList{Location(Source{{12, 34}}, 1)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: attribute is not valid for compute shader output");
|
||||
}
|
||||
|
||||
TEST_F(LocationAttributeTests, ComputeShaderLocation_Output) {
|
||||
auto* input = Param("input", ty.i32(),
|
||||
ast::AttributeList{Location(Source{{12, 34}}, 0u)});
|
||||
Func("main", {input}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
create<ast::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: attribute is not valid for compute shader inputs");
|
||||
}
|
||||
|
||||
TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Output) {
|
||||
auto* m =
|
||||
Member("m", ty.i32(), ast::AttributeList{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::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: attribute is not valid for compute shader output\n"
|
||||
"56:78 note: while analysing entry point 'main'");
|
||||
}
|
||||
|
||||
TEST_F(LocationAttributeTests, ComputeShaderLocationStructMember_Input) {
|
||||
auto* m =
|
||||
Member("m", ty.i32(), ast::AttributeList{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::WorkgroupAttribute>(Source{{12, 34}}, Expr(1))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: attribute is not valid for compute shader inputs\n"
|
||||
"56:78 note: while analysing entry point 'main'");
|
||||
}
|
||||
|
||||
TEST_F(LocationAttributeTests, Duplicate_input) {
|
||||
// @stage(fragment)
|
||||
// fn main(@location(1) param_a : f32,
|
||||
// @location(1) param_b : f32) {}
|
||||
auto* param_a = Param("param_a", ty.f32(), {Location(1)});
|
||||
auto* param_b = Param("param_b", ty.f32(), {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(LocationAttributeTests, 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 LocationAttributeTests
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
830
src/tint/resolver/function_validation_test.cc
Normal file
830
src/tint/resolver/function_validation_test.cc
Normal file
@@ -0,0 +1,830 @@
|
||||
// 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/tint/ast/discard_statement.h"
|
||||
#include "src/tint/ast/return_statement.h"
|
||||
#include "src/tint/ast/stage_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace {
|
||||
|
||||
class ResolverFunctionValidationTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, DuplicateParameterName) {
|
||||
// fn func_a(common_name : f32) { }
|
||||
// fn func_b(common_name : f32) { }
|
||||
Func("func_a", {Param("common_name", ty.f32())}, ty.void_(), {});
|
||||
Func("func_b", {Param("common_name", ty.f32())}, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ParameterMayShadowGlobal) {
|
||||
// var<private> common_name : f32;
|
||||
// fn func(common_name : f32) { }
|
||||
Global("common_name", ty.f32(), ast::StorageClass::kPrivate);
|
||||
Func("func", {Param("common_name", ty.f32())}, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, LocalConflictsWithParameter) {
|
||||
// fn func(common_name : f32) {
|
||||
// let common_name = 1;
|
||||
// }
|
||||
Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
|
||||
{Decl(Const(Source{{56, 78}}, "common_name", nullptr, Expr(1)))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), R"(56:78 error: redeclaration of 'common_name'
|
||||
12:34 note: 'common_name' previously declared here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, NestedLocalMayShadowParameter) {
|
||||
// fn func(common_name : f32) {
|
||||
// {
|
||||
// let common_name = 1;
|
||||
// }
|
||||
// }
|
||||
Func("func", {Param(Source{{12, 34}}, "common_name", ty.f32())}, ty.void_(),
|
||||
{Block(Decl(Const(Source{{56, 78}}, "common_name", nullptr, Expr(1))))});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
VoidFunctionEndWithoutReturnStatement_Pass) {
|
||||
// fn func { var a:i32 = 2; }
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
Decl(var),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, FunctionUsingSameVariableName_Pass) {
|
||||
// fn func() -> i32 {
|
||||
// var func:i32 = 0;
|
||||
// return func;
|
||||
// }
|
||||
|
||||
auto* var = Var("func", ty.i32(), Expr(0));
|
||||
Func("func", ast::VariableList{}, ty.i32(),
|
||||
ast::StatementList{
|
||||
Decl(var),
|
||||
Return(Source{{12, 34}}, Expr("func")),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionNameSameAsFunctionScopeVariableName_Pass) {
|
||||
// fn a() -> void { var b:i32 = 0; }
|
||||
// fn b() -> i32 { return 2; }
|
||||
|
||||
auto* var = Var("b", ty.i32(), Expr(0));
|
||||
Func("a", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
Decl(var),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
Func(Source{{12, 34}}, "b", ast::VariableList{}, ty.i32(),
|
||||
ast::StatementList{
|
||||
Return(2),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, UnreachableCode_return) {
|
||||
// fn func() -> {
|
||||
// var a : i32;
|
||||
// return;
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* ret = Return();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(), {decl_a, ret, assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(ret)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, UnreachableCode_return_InBlocks) {
|
||||
// fn func() -> {
|
||||
// var a : i32;
|
||||
// {{{return;}}}
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* ret = Return();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
{decl_a, Block(Block(Block(ret))), assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(ret)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard) {
|
||||
// fn func() -> {
|
||||
// var a : i32;
|
||||
// discard;
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* discard = Discard();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(), {decl_a, discard, assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(discard)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, UnreachableCode_discard_InBlocks) {
|
||||
// fn func() -> {
|
||||
// var a : i32;
|
||||
// {{{discard;}}}
|
||||
// a = 2;
|
||||
//}
|
||||
|
||||
auto* decl_a = Decl(Var("a", ty.i32()));
|
||||
auto* discard = Discard();
|
||||
auto* assign_a = Assign(Source{{12, 34}}, "a", 2);
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
{decl_a, Block(Block(Block(discard))), assign_a});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 warning: code is unreachable");
|
||||
EXPECT_TRUE(Sem().Get(decl_a)->IsReachable());
|
||||
EXPECT_TRUE(Sem().Get(discard)->IsReachable());
|
||||
EXPECT_FALSE(Sem().Get(assign_a)->IsReachable());
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatement_Fail) {
|
||||
// fn func() -> int { var a:i32 = 2; }
|
||||
|
||||
auto* var = Var("a", ty.i32(), Expr(2));
|
||||
|
||||
Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.i32(),
|
||||
ast::StatementList{
|
||||
Decl(var),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
VoidFunctionEndWithoutReturnStatementEmptyBody_Pass) {
|
||||
// fn func {}
|
||||
|
||||
Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionEndWithoutReturnStatementEmptyBody_Fail) {
|
||||
// fn func() -> int {}
|
||||
|
||||
Func(Source{{12, 34}}, "func", ast::VariableList{}, ty.i32(),
|
||||
ast::StatementList{}, ast::AttributeList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing return at end of function");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementType_Pass) {
|
||||
// fn func { return; }
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
Return(),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementType_fail) {
|
||||
// fn func { return 2; }
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
Return(Source{{12, 34}}, Expr(2)),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: return statement type must match its function return "
|
||||
"type, returned 'i32', expected 'void'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementType_void_fail) {
|
||||
// fn v { return; }
|
||||
// fn func { return v(); }
|
||||
Func("v", {}, ty.void_(), {Return()});
|
||||
Func("func", {}, ty.void_(),
|
||||
{
|
||||
Return(Call(Source{{12, 34}}, "v")),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: function 'v' does not return a value");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementTypeMissing_fail) {
|
||||
// fn func() -> f32 { return; }
|
||||
Func("func", ast::VariableList{}, ty.f32(),
|
||||
ast::StatementList{
|
||||
Return(Source{{12, 34}}, nullptr),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: return statement type must match its function return "
|
||||
"type, returned 'void', expected 'f32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementTypeF32_pass) {
|
||||
// fn func() -> f32 { return 2.0; }
|
||||
Func("func", ast::VariableList{}, ty.f32(),
|
||||
ast::StatementList{
|
||||
Return(Source{{12, 34}}, Expr(2.f)),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementTypeF32_fail) {
|
||||
// fn func() -> f32 { return 2; }
|
||||
Func("func", ast::VariableList{}, ty.f32(),
|
||||
ast::StatementList{
|
||||
Return(Source{{12, 34}}, Expr(2)),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: return statement type must match its function return "
|
||||
"type, returned 'i32', expected 'f32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementTypeF32Alias_pass) {
|
||||
// type myf32 = f32;
|
||||
// fn func() -> myf32 { return 2.0; }
|
||||
auto* myf32 = Alias("myf32", ty.f32());
|
||||
Func("func", ast::VariableList{}, ty.Of(myf32),
|
||||
ast::StatementList{
|
||||
Return(Source{{12, 34}}, Expr(2.f)),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
FunctionTypeMustMatchReturnStatementTypeF32Alias_fail) {
|
||||
// type myf32 = f32;
|
||||
// fn func() -> myf32 { return 2; }
|
||||
auto* myf32 = Alias("myf32", ty.f32());
|
||||
Func("func", ast::VariableList{}, ty.Of(myf32),
|
||||
ast::StatementList{
|
||||
Return(Source{{12, 34}}, Expr(2u)),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: return statement type must match its function return "
|
||||
"type, returned 'u32', expected 'f32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, CannotCallEntryPoint) {
|
||||
// @stage(compute) @workgroup_size(1) fn entrypoint() {}
|
||||
// fn func() { return entrypoint(); }
|
||||
Func("entrypoint", ast::VariableList{}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
|
||||
|
||||
Func("func", ast::VariableList{}, ty.void_(),
|
||||
{
|
||||
CallStmt(Call(Source{{12, 34}}, "entrypoint")),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
|
||||
R"(12:34 error: entry point functions cannot be the target of a function call)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, PipelineStage_MustBeUnique_Fail) {
|
||||
// @stage(fragment)
|
||||
// @stage(vertex)
|
||||
// fn main() { return; }
|
||||
Func(Source{{12, 34}}, "main", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
Return(),
|
||||
},
|
||||
ast::AttributeList{
|
||||
Stage(Source{{12, 34}}, ast::PipelineStage::kVertex),
|
||||
Stage(Source{{56, 78}}, ast::PipelineStage::kFragment),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
R"(56:78 error: duplicate stage attribute
|
||||
12:34 note: first attribute declared here)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, NoPipelineEntryPoints) {
|
||||
Func("vtx_func", ast::VariableList{}, ty.void_(),
|
||||
ast::StatementList{
|
||||
Return(),
|
||||
},
|
||||
ast::AttributeList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, FunctionVarInitWithParam) {
|
||||
// fn foo(bar : f32){
|
||||
// var baz : f32 = bar;
|
||||
// }
|
||||
|
||||
auto* bar = Param("bar", ty.f32());
|
||||
auto* baz = Var("baz", ty.f32(), Expr("bar"));
|
||||
|
||||
Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
|
||||
ast::AttributeList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, FunctionConstInitWithParam) {
|
||||
// fn foo(bar : f32){
|
||||
// let baz : f32 = bar;
|
||||
// }
|
||||
|
||||
auto* bar = Param("bar", ty.f32());
|
||||
auto* baz = Const("baz", ty.f32(), Expr("bar"));
|
||||
|
||||
Func("foo", ast::VariableList{bar}, ty.void_(), ast::StatementList{Decl(baz)},
|
||||
ast::AttributeList{});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, FunctionParamsConst) {
|
||||
Func("foo", {Param(Sym("arg"), ty.i32())}, ty.void_(),
|
||||
{Assign(Expr(Source{{12, 34}}, "arg"), Expr(1)), Return()});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot assign to function parameter\nnote: 'arg' is "
|
||||
"declared here:");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_GoodType_ConstU32) {
|
||||
// let x = 4u;
|
||||
// let x = 8u;
|
||||
// @stage(compute) @workgroup_size(x, y, 16u)
|
||||
// fn main() {}
|
||||
auto* x = GlobalConst("x", ty.u32(), Expr(4u));
|
||||
auto* y = GlobalConst("y", ty.u32(), Expr(8u));
|
||||
auto* func = Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr("x"), Expr("y"), Expr(16u))});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem_func = Sem().Get(func);
|
||||
auto* sem_x = Sem().Get<sem::GlobalVariable>(x);
|
||||
auto* sem_y = Sem().Get<sem::GlobalVariable>(y);
|
||||
|
||||
ASSERT_NE(sem_func, nullptr);
|
||||
ASSERT_NE(sem_x, nullptr);
|
||||
ASSERT_NE(sem_y, nullptr);
|
||||
|
||||
EXPECT_TRUE(sem_func->DirectlyReferencedGlobals().contains(sem_x));
|
||||
EXPECT_TRUE(sem_func->DirectlyReferencedGlobals().contains(sem_y));
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_GoodType_U32) {
|
||||
// [[stage(compute), workgroup_size(1u, 2u, 3u)]
|
||||
// fn main() {}
|
||||
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Source{{12, 34}}, Expr(1u), Expr(2u), Expr(3u))});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_MismatchTypeU32) {
|
||||
// [[stage(compute), workgroup_size(1u, 2u, 3)]
|
||||
// fn main() {}
|
||||
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(1u), Expr(2u), Expr(Source{{12, 34}}, 3))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size arguments must be of the same type, "
|
||||
"either i32 or u32");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_MismatchTypeI32) {
|
||||
// [[stage(compute), workgroup_size(1, 2u, 3)]
|
||||
// fn main() {}
|
||||
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(1), Expr(Source{{12, 34}}, 2u), Expr(3))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size arguments must be of the same type, "
|
||||
"either i32 or u32");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_TypeMismatch) {
|
||||
// let x = 64u;
|
||||
// [[stage(compute), workgroup_size(1, x)]
|
||||
// fn main() {}
|
||||
GlobalConst("x", ty.u32(), Expr(64u));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(1), Expr(Source{{12, 34}}, "x"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size arguments must be of the same type, "
|
||||
"either i32 or u32");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_TypeMismatch2) {
|
||||
// let x = 64u;
|
||||
// let y = 32;
|
||||
// [[stage(compute), workgroup_size(x, y)]
|
||||
// fn main() {}
|
||||
GlobalConst("x", ty.u32(), Expr(64u));
|
||||
GlobalConst("y", ty.i32(), Expr(32));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr("x"), Expr(Source{{12, 34}}, "y"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size arguments must be of the same type, "
|
||||
"either i32 or u32");
|
||||
}
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Mismatch_ConstU32) {
|
||||
// let x = 4u;
|
||||
// let x = 8u;
|
||||
// [[stage(compute), workgroup_size(x, y, 16]
|
||||
// fn main() {}
|
||||
GlobalConst("x", ty.u32(), Expr(4u));
|
||||
GlobalConst("y", ty.u32(), Expr(8u));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr("x"), Expr("y"), Expr(Source{{12, 34}}, 16))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size arguments must be of the same type, "
|
||||
"either i32 or u32");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_BadType) {
|
||||
// [[stage(compute), workgroup_size(64.0)]
|
||||
// fn main() {}
|
||||
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, 64.f))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be either literal or "
|
||||
"module-scope constant of type i32 or u32");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_Negative) {
|
||||
// [[stage(compute), workgroup_size(-2)]
|
||||
// fn main() {}
|
||||
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, -2))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be at least 1");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Literal_Zero) {
|
||||
// [[stage(compute), workgroup_size(0)]
|
||||
// fn main() {}
|
||||
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, 0))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be at least 1");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_BadType) {
|
||||
// let x = 64.0;
|
||||
// [[stage(compute), workgroup_size(x)]
|
||||
// fn main() {}
|
||||
GlobalConst("x", ty.f32(), Expr(64.f));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be either literal or "
|
||||
"module-scope constant of type i32 or u32");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_Negative) {
|
||||
// let x = -2;
|
||||
// [[stage(compute), workgroup_size(x)]
|
||||
// fn main() {}
|
||||
GlobalConst("x", ty.i32(), Expr(-2));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be at least 1");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_Const_Zero) {
|
||||
// let x = 0;
|
||||
// [[stage(compute), workgroup_size(x)]
|
||||
// fn main() {}
|
||||
GlobalConst("x", ty.i32(), Expr(0));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be at least 1");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest,
|
||||
WorkgroupSize_Const_NestedZeroValueConstructor) {
|
||||
// let x = i32(i32(i32()));
|
||||
// [[stage(compute), workgroup_size(x)]
|
||||
// fn main() {}
|
||||
GlobalConst("x", ty.i32(),
|
||||
Construct(ty.i32(), Construct(ty.i32(), Construct(ty.i32()))));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be at least 1");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_NonConst) {
|
||||
// var<private> x = 0;
|
||||
// [[stage(compute), workgroup_size(x)]
|
||||
// fn main() {}
|
||||
Global("x", ty.i32(), ast::StorageClass::kPrivate, Expr(64));
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Expr(Source{{12, 34}}, "x"))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be either literal or "
|
||||
"module-scope constant of type i32 or u32");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, WorkgroupSize_InvalidExpr) {
|
||||
// [[stage(compute), workgroup_size(i32(1))]
|
||||
// fn main() {}
|
||||
Func("main", {}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute),
|
||||
WorkgroupSize(Construct(Source{{12, 34}}, ty.i32(), 1))});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: workgroup_size argument must be either a literal or "
|
||||
"a module-scope constant");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_NonPlain) {
|
||||
auto* ret_type =
|
||||
ty.pointer(Source{{12, 34}}, ty.i32(), ast::StorageClass::kFunction);
|
||||
Func("f", {}, ret_type, {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function return type must be a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_AtomicInt) {
|
||||
auto* ret_type = ty.atomic(Source{{12, 34}}, ty.i32());
|
||||
Func("f", {}, ret_type, {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function return type must be a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_ArrayOfAtomic) {
|
||||
auto* ret_type = ty.array(Source{{12, 34}}, ty.atomic(ty.i32()), 10);
|
||||
Func("f", {}, ret_type, {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function return type must be a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_StructOfAtomic) {
|
||||
Structure("S", {Member("m", ty.atomic(ty.i32()))});
|
||||
auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
|
||||
Func("f", {}, ret_type, {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function return type must be a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ReturnIsConstructible_RuntimeArray) {
|
||||
auto* ret_type = ty.array(Source{{12, 34}}, ty.i32());
|
||||
Func("f", {}, ret_type, {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function return type must be a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ParameterStoreType_NonAtomicFree) {
|
||||
Structure("S", {Member("m", ty.atomic(ty.i32()))});
|
||||
auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
|
||||
auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
|
||||
Func("f", ast::VariableList{bar}, ty.void_(), {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: store type of function parameter must be a "
|
||||
"constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ParameterSotreType_AtomicFree) {
|
||||
Structure("S", {Member("m", ty.i32())});
|
||||
auto* ret_type = ty.type_name(Source{{12, 34}}, "S");
|
||||
auto* bar = Param(Source{{12, 34}}, "bar", ret_type);
|
||||
Func("f", ast::VariableList{bar}, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ParametersAtLimit) {
|
||||
ast::VariableList params;
|
||||
for (int i = 0; i < 255; i++) {
|
||||
params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
|
||||
}
|
||||
Func(Source{{12, 34}}, "f", params, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ParametersOverLimit) {
|
||||
ast::VariableList params;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
params.emplace_back(Param("param_" + std::to_string(i), ty.i32()));
|
||||
}
|
||||
Func(Source{{12, 34}}, "f", params, ty.void_(), {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: functions may declare at most 255 parameters");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ParameterVectorNoType) {
|
||||
// fn f(p : vec3) {}
|
||||
|
||||
Func(Source{{12, 34}}, "f",
|
||||
{Param("p", create<ast::Vector>(Source{{12, 34}}, nullptr, 3))},
|
||||
ty.void_(), {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverFunctionValidationTest, ParameterMatrixNoType) {
|
||||
// fn f(p : vec3) {}
|
||||
|
||||
Func(Source{{12, 34}}, "f",
|
||||
{Param("p", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3))},
|
||||
ty.void_(), {});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
|
||||
}
|
||||
|
||||
struct TestParams {
|
||||
ast::StorageClass storage_class;
|
||||
bool should_pass;
|
||||
};
|
||||
|
||||
struct TestWithParams : resolver::ResolverTestWithParam<TestParams> {};
|
||||
|
||||
using ResolverFunctionParameterValidationTest = TestWithParams;
|
||||
TEST_P(ResolverFunctionParameterValidationTest, StorageClass) {
|
||||
auto& param = GetParam();
|
||||
auto* ptr_type = ty.pointer(Source{{12, 34}}, ty.i32(), param.storage_class);
|
||||
auto* arg = Param(Source{{12, 34}}, "p", ptr_type);
|
||||
Func("f", ast::VariableList{arg}, ty.void_(), {});
|
||||
|
||||
if (param.should_pass) {
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
} else {
|
||||
std::stringstream ss;
|
||||
ss << param.storage_class;
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function parameter of pointer type cannot be in '" +
|
||||
ss.str() + "' storage class");
|
||||
}
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
ResolverTest,
|
||||
ResolverFunctionParameterValidationTest,
|
||||
testing::Values(TestParams{ast::StorageClass::kNone, false},
|
||||
TestParams{ast::StorageClass::kInput, false},
|
||||
TestParams{ast::StorageClass::kOutput, false},
|
||||
TestParams{ast::StorageClass::kUniform, false},
|
||||
TestParams{ast::StorageClass::kWorkgroup, true},
|
||||
TestParams{ast::StorageClass::kUniformConstant, false},
|
||||
TestParams{ast::StorageClass::kStorage, false},
|
||||
TestParams{ast::StorageClass::kPrivate, true},
|
||||
TestParams{ast::StorageClass::kFunction, true}));
|
||||
|
||||
} // namespace
|
||||
} // namespace tint
|
||||
151
src/tint/resolver/host_shareable_validation_test.cc
Normal file
151
src/tint/resolver/host_shareable_validation_test.cc
Normal file
@@ -0,0 +1,151 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/struct.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverHostShareableValidationTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverHostShareableValidationTest, BoolMember) {
|
||||
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.bool_())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
|
||||
12:34 note: while analysing structure member S.x
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
|
||||
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.vec3<bool>())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'vec3<bool>' cannot be used in storage class 'storage' as it is non-host-shareable
|
||||
12:34 note: while analysing structure member S.x
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverHostShareableValidationTest, Aliases) {
|
||||
auto* a1 = Alias("a1", ty.bool_());
|
||||
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.Of(a1))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* a2 = Alias("a2", ty.Of(s));
|
||||
Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
|
||||
12:34 note: while analysing structure member S.x
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
|
||||
auto* i1 = Structure("I1", {Member(Source{{1, 2}}, "x", ty.bool_())});
|
||||
auto* i2 = Structure("I2", {Member(Source{{3, 4}}, "y", ty.Of(i1))});
|
||||
auto* i3 = Structure("I3", {Member(Source{{5, 6}}, "z", ty.Of(i2))});
|
||||
|
||||
auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
|
||||
Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(9:10 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
|
||||
1:2 note: while analysing structure member I1.x
|
||||
3:4 note: while analysing structure member I2.y
|
||||
5:6 note: while analysing structure member I3.z
|
||||
7:8 note: while analysing structure member S.m
|
||||
9:10 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverHostShareableValidationTest, NoError) {
|
||||
auto* i1 =
|
||||
Structure("I1", {
|
||||
Member(Source{{1, 1}}, "x1", ty.f32()),
|
||||
Member(Source{{2, 1}}, "y1", ty.vec3<f32>()),
|
||||
Member(Source{{3, 1}}, "z1", ty.array<i32, 4>()),
|
||||
});
|
||||
auto* a1 = Alias("a1", ty.Of(i1));
|
||||
auto* i2 = Structure("I2", {
|
||||
Member(Source{{4, 1}}, "x2", ty.mat2x2<f32>()),
|
||||
Member(Source{{5, 1}}, "y2", ty.Of(i1)),
|
||||
});
|
||||
auto* a2 = Alias("a2", ty.Of(i2));
|
||||
auto* i3 = Structure("I3", {
|
||||
Member(Source{{4, 1}}, "x3", ty.Of(a1)),
|
||||
Member(Source{{5, 1}}, "y3", ty.Of(i2)),
|
||||
Member(Source{{6, 1}}, "z3", ty.Of(a2)),
|
||||
});
|
||||
|
||||
auto* s = Structure("S", {Member(Source{{7, 8}}, "m", ty.Of(i3))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
|
||||
Global(Source{{9, 10}}, "g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
WrapInFunction();
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
176
src/tint/resolver/inferred_type_test.cc
Normal file
176
src/tint/resolver/inferred_type_test.cc
Normal file
@@ -0,0 +1,176 @@
|
||||
// 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/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.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 <typename T>
|
||||
using alias = builder::alias<T>;
|
||||
using f32 = builder::f32;
|
||||
using i32 = builder::i32;
|
||||
using u32 = builder::u32;
|
||||
|
||||
struct ResolverInferredTypeTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
struct Params {
|
||||
builder::ast_expr_func_ptr create_value;
|
||||
builder::sem_type_func_ptr create_expected_type;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr Params ParamsFor() {
|
||||
return Params{DataType<T>::Expr, DataType<T>::Sem};
|
||||
}
|
||||
|
||||
Params all_cases[] = {
|
||||
ParamsFor<bool>(), //
|
||||
ParamsFor<u32>(), //
|
||||
ParamsFor<i32>(), //
|
||||
ParamsFor<f32>(), //
|
||||
ParamsFor<vec3<bool>>(), //
|
||||
ParamsFor<vec3<i32>>(), //
|
||||
ParamsFor<vec3<u32>>(), //
|
||||
ParamsFor<vec3<f32>>(), //
|
||||
ParamsFor<mat3x3<f32>>(), //
|
||||
ParamsFor<alias<bool>>(), //
|
||||
ParamsFor<alias<u32>>(), //
|
||||
ParamsFor<alias<i32>>(), //
|
||||
ParamsFor<alias<f32>>(), //
|
||||
ParamsFor<alias<vec3<bool>>>(), //
|
||||
ParamsFor<alias<vec3<i32>>>(), //
|
||||
ParamsFor<alias<vec3<u32>>>(), //
|
||||
ParamsFor<alias<vec3<f32>>>(), //
|
||||
ParamsFor<alias<mat3x3<f32>>>(), //
|
||||
};
|
||||
|
||||
using ResolverInferredTypeParamTest = ResolverTestWithParam<Params>;
|
||||
|
||||
TEST_P(ResolverInferredTypeParamTest, GlobalLet_Pass) {
|
||||
auto& params = GetParam();
|
||||
|
||||
auto* expected_type = params.create_expected_type(*this);
|
||||
|
||||
// let a = <type constructor>;
|
||||
auto* ctor_expr = params.create_value(*this, 0);
|
||||
auto* var = GlobalConst("a", nullptr, ctor_expr);
|
||||
WrapInFunction();
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(TypeOf(var), expected_type);
|
||||
}
|
||||
|
||||
TEST_P(ResolverInferredTypeParamTest, GlobalVar_Fail) {
|
||||
auto& params = GetParam();
|
||||
|
||||
// var a = <type constructor>;
|
||||
auto* ctor_expr = params.create_value(*this, 0);
|
||||
Global(Source{{12, 34}}, "a", nullptr, ast::StorageClass::kPrivate,
|
||||
ctor_expr);
|
||||
WrapInFunction();
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: global var declaration must specify a type");
|
||||
}
|
||||
|
||||
TEST_P(ResolverInferredTypeParamTest, LocalLet_Pass) {
|
||||
auto& params = GetParam();
|
||||
|
||||
auto* expected_type = params.create_expected_type(*this);
|
||||
|
||||
// let a = <type constructor>;
|
||||
auto* ctor_expr = params.create_value(*this, 0);
|
||||
auto* var = Const("a", nullptr, ctor_expr);
|
||||
WrapInFunction(var);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(TypeOf(var), expected_type);
|
||||
}
|
||||
|
||||
TEST_P(ResolverInferredTypeParamTest, LocalVar_Pass) {
|
||||
auto& params = GetParam();
|
||||
|
||||
auto* expected_type = params.create_expected_type(*this);
|
||||
|
||||
// var a = <type constructor>;
|
||||
auto* ctor_expr = params.create_value(*this, 0);
|
||||
auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
|
||||
WrapInFunction(var);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverTest,
|
||||
ResolverInferredTypeParamTest,
|
||||
testing::ValuesIn(all_cases));
|
||||
|
||||
TEST_F(ResolverInferredTypeTest, InferArray_Pass) {
|
||||
auto* type = ty.array(ty.u32(), 10);
|
||||
auto* expected_type =
|
||||
create<sem::Array>(create<sem::U32>(), 10, 4, 4 * 10, 4, 4);
|
||||
|
||||
auto* ctor_expr = Construct(type);
|
||||
auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
|
||||
WrapInFunction(var);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
|
||||
}
|
||||
|
||||
TEST_F(ResolverInferredTypeTest, InferStruct_Pass) {
|
||||
auto* member = Member("x", ty.i32());
|
||||
auto* str = Structure("S", {member}, {create<ast::StructBlockAttribute>()});
|
||||
|
||||
auto* expected_type = create<sem::Struct>(
|
||||
str, str->name,
|
||||
sem::StructMemberList{create<sem::StructMember>(
|
||||
member, member->symbol, create<sem::I32>(), 0, 0, 0, 4)},
|
||||
0, 4, 4);
|
||||
|
||||
auto* ctor_expr = Construct(ty.Of(str));
|
||||
|
||||
auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
|
||||
WrapInFunction(var);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
115
src/tint/resolver/is_host_shareable_test.cc
Normal file
115
src/tint/resolver/is_host_shareable_test.cc
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/atomic_type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverIsHostShareable = ResolverTest;
|
||||
|
||||
TEST_F(ResolverIsHostShareable, Void) {
|
||||
EXPECT_FALSE(r()->IsHostShareable(create<sem::Void>()));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, Bool) {
|
||||
EXPECT_FALSE(r()->IsHostShareable(create<sem::Bool>()));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, NumericScalar) {
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::I32>()));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::U32>()));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::F32>()));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, NumericVector) {
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 2)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 3)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::I32>(), 4)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 2)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 3)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::U32>(), 4)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 2)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 3)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Vector>(create<sem::F32>(), 4)));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, BoolVector) {
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 2)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 3)));
|
||||
EXPECT_FALSE(
|
||||
r()->IsHostShareable(create<sem::Vector>(create<sem::Bool>(), 4)));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, Matrix) {
|
||||
auto* vec2 = create<sem::Vector>(create<sem::F32>(), 2);
|
||||
auto* vec3 = create<sem::Vector>(create<sem::F32>(), 3);
|
||||
auto* vec4 = create<sem::Vector>(create<sem::F32>(), 4);
|
||||
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 2)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 3)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec2, 4)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 2)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 3)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec3, 4)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 2)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 3)));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Matrix>(vec4, 4)));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, Pointer) {
|
||||
auto* ptr = create<sem::Pointer>(
|
||||
create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
|
||||
EXPECT_FALSE(r()->IsHostShareable(ptr));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, Atomic) {
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::I32>())));
|
||||
EXPECT_TRUE(r()->IsHostShareable(create<sem::Atomic>(create<sem::U32>())));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, ArraySizedOfHostShareable) {
|
||||
auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, 4);
|
||||
EXPECT_TRUE(r()->IsHostShareable(arr));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsHostShareable, ArrayUnsizedOfHostShareable) {
|
||||
auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, 4);
|
||||
EXPECT_TRUE(r()->IsHostShareable(arr));
|
||||
}
|
||||
|
||||
// Note: Structure tests covered in host_shareable_validation_test.cc
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
140
src/tint/resolver/is_storeable_test.cc
Normal file
140
src/tint/resolver/is_storeable_test.cc
Normal file
@@ -0,0 +1,140 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/atomic_type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverIsStorableTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Void) {
|
||||
EXPECT_FALSE(r()->IsStorable(create<sem::Void>()));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Scalar) {
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Bool>()));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::I32>()));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::U32>()));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::F32>()));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Vector) {
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 2)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 3)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::I32>(), 4)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 2)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 3)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::U32>(), 4)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 2)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 3)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Vector>(create<sem::F32>(), 4)));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Matrix) {
|
||||
auto* vec2 = create<sem::Vector>(create<sem::F32>(), 2);
|
||||
auto* vec3 = create<sem::Vector>(create<sem::F32>(), 3);
|
||||
auto* vec4 = create<sem::Vector>(create<sem::F32>(), 4);
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 2)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 3)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec2, 4)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 2)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 3)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec3, 4)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 2)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 3)));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Matrix>(vec4, 4)));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Pointer) {
|
||||
auto* ptr = create<sem::Pointer>(
|
||||
create<sem::I32>(), ast::StorageClass::kPrivate, ast::Access::kReadWrite);
|
||||
EXPECT_FALSE(r()->IsStorable(ptr));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Atomic) {
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::I32>())));
|
||||
EXPECT_TRUE(r()->IsStorable(create<sem::Atomic>(create<sem::U32>())));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, ArraySizedOfStorable) {
|
||||
auto* arr = create<sem::Array>(create<sem::I32>(), 5, 4, 20, 4, 4);
|
||||
EXPECT_TRUE(r()->IsStorable(arr));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, ArrayUnsizedOfStorable) {
|
||||
auto* arr = create<sem::Array>(create<sem::I32>(), 0, 4, 4, 4, 4);
|
||||
EXPECT_TRUE(r()->IsStorable(arr));
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Struct_AllMembersStorable) {
|
||||
Structure("S", {
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.f32()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Struct_SomeMembersNonStorable) {
|
||||
Structure("S", {
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Struct_NestedStorable) {
|
||||
auto* storable = Structure("Storable", {
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.f32()),
|
||||
});
|
||||
Structure("S", {
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.Of(storable)),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverIsStorableTest, Struct_NestedNonStorable) {
|
||||
auto* non_storable =
|
||||
Structure("nonstorable",
|
||||
{
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.pointer<i32>(ast::StorageClass::kPrivate)),
|
||||
});
|
||||
Structure("S", {
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.Of(non_storable)),
|
||||
});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(error: ptr<private, i32, read_write> cannot be used as the type of a structure member)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
108
src/tint/resolver/pipeline_overridable_constant_test.cc
Normal file
108
src/tint/resolver/pipeline_overridable_constant_test.cc
Normal file
@@ -0,0 +1,108 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
class ResolverPipelineOverridableConstantTest : public ResolverTest {
|
||||
protected:
|
||||
/// Verify that the AST node `var` was resolved to an overridable constant
|
||||
/// with an ID equal to `id`.
|
||||
/// @param var the overridable constant AST node
|
||||
/// @param id the expected constant ID
|
||||
void ExpectConstantId(const ast::Variable* var, uint16_t id) {
|
||||
auto* sem = Sem().Get<sem::GlobalVariable>(var);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Declaration(), var);
|
||||
EXPECT_TRUE(sem->IsOverridable());
|
||||
EXPECT_EQ(sem->ConstantId(), id);
|
||||
EXPECT_FALSE(sem->ConstantValue());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ResolverPipelineOverridableConstantTest, NonOverridable) {
|
||||
auto* a = GlobalConst("a", ty.f32(), Expr(1.f));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem_a = Sem().Get<sem::GlobalVariable>(a);
|
||||
ASSERT_NE(sem_a, nullptr);
|
||||
EXPECT_EQ(sem_a->Declaration(), a);
|
||||
EXPECT_FALSE(sem_a->IsOverridable());
|
||||
EXPECT_TRUE(sem_a->ConstantValue());
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineOverridableConstantTest, WithId) {
|
||||
auto* a = Override("a", ty.f32(), Expr(1.f), {Id(7u)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ExpectConstantId(a, 7u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineOverridableConstantTest, WithoutId) {
|
||||
auto* a = Override("a", ty.f32(), Expr(1.f));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ExpectConstantId(a, 0u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineOverridableConstantTest, WithAndWithoutIds) {
|
||||
std::vector<ast::Variable*> variables;
|
||||
auto* a = Override("a", ty.f32(), Expr(1.f));
|
||||
auto* b = Override("b", ty.f32(), Expr(1.f));
|
||||
auto* c = Override("c", ty.f32(), Expr(1.f), {Id(2u)});
|
||||
auto* d = Override("d", ty.f32(), Expr(1.f), {Id(4u)});
|
||||
auto* e = Override("e", ty.f32(), Expr(1.f));
|
||||
auto* f = Override("f", ty.f32(), Expr(1.f), {Id(1u)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
// Verify that constant id allocation order is deterministic.
|
||||
ExpectConstantId(a, 0u);
|
||||
ExpectConstantId(b, 3u);
|
||||
ExpectConstantId(c, 2u);
|
||||
ExpectConstantId(d, 4u);
|
||||
ExpectConstantId(e, 5u);
|
||||
ExpectConstantId(f, 1u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineOverridableConstantTest, DuplicateIds) {
|
||||
Override("a", ty.f32(), Expr(1.f), {Id(Source{{12, 34}}, 7u)});
|
||||
Override("b", ty.f32(), Expr(1.f), {Id(Source{{56, 78}}, 7u)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), R"(56:78 error: pipeline constant IDs must be unique
|
||||
12:34 note: a pipeline constant with an ID of 7 was previously declared here:)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineOverridableConstantTest, IdTooLarge) {
|
||||
Override("a", ty.f32(), Expr(1.f), {Id(Source{{12, 34}}, 65536u)});
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: pipeline constant IDs must be between 0 and 65535");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
126
src/tint/resolver/ptr_ref_test.cc
Normal file
126
src/tint/resolver/ptr_ref_test.cc
Normal file
@@ -0,0 +1,126 @@
|
||||
// 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/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/reference_type.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
struct ResolverPtrRefTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverPtrRefTest, AddressOf) {
|
||||
// var v : i32;
|
||||
// &v
|
||||
|
||||
auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
|
||||
auto* expr = AddressOf(v);
|
||||
|
||||
WrapInFunction(v, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(expr)->Is<sem::Pointer>());
|
||||
EXPECT_TRUE(TypeOf(expr)->As<sem::Pointer>()->StoreType()->Is<sem::I32>());
|
||||
EXPECT_EQ(TypeOf(expr)->As<sem::Pointer>()->StorageClass(),
|
||||
ast::StorageClass::kFunction);
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefTest, AddressOfThenDeref) {
|
||||
// var v : i32;
|
||||
// *(&v)
|
||||
|
||||
auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
|
||||
auto* expr = Deref(AddressOf(v));
|
||||
|
||||
WrapInFunction(v, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(expr)->Is<sem::Reference>());
|
||||
EXPECT_TRUE(TypeOf(expr)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefTest, DefaultPtrStorageClass) {
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
|
||||
|
||||
auto* buf = Structure("S", {Member("m", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* function = Var("f", ty.i32());
|
||||
auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
|
||||
auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
|
||||
auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(1),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
auto* function_ptr =
|
||||
Const("f_ptr", ty.pointer(ty.i32(), ast::StorageClass::kFunction),
|
||||
AddressOf(function));
|
||||
auto* private_ptr =
|
||||
Const("p_ptr", ty.pointer(ty.i32(), ast::StorageClass::kPrivate),
|
||||
AddressOf(private_));
|
||||
auto* workgroup_ptr =
|
||||
Const("w_ptr", ty.pointer(ty.i32(), ast::StorageClass::kWorkgroup),
|
||||
AddressOf(workgroup));
|
||||
auto* uniform_ptr =
|
||||
Const("ub_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kUniform),
|
||||
AddressOf(uniform));
|
||||
auto* storage_ptr =
|
||||
Const("sb_ptr", ty.pointer(ty.Of(buf), ast::StorageClass::kStorage),
|
||||
AddressOf(storage));
|
||||
|
||||
WrapInFunction(function, function_ptr, private_ptr, workgroup_ptr,
|
||||
uniform_ptr, storage_ptr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(function_ptr)->Is<sem::Pointer>())
|
||||
<< "function_ptr is " << TypeOf(function_ptr)->TypeInfo().name;
|
||||
ASSERT_TRUE(TypeOf(private_ptr)->Is<sem::Pointer>())
|
||||
<< "private_ptr is " << TypeOf(private_ptr)->TypeInfo().name;
|
||||
ASSERT_TRUE(TypeOf(workgroup_ptr)->Is<sem::Pointer>())
|
||||
<< "workgroup_ptr is " << TypeOf(workgroup_ptr)->TypeInfo().name;
|
||||
ASSERT_TRUE(TypeOf(uniform_ptr)->Is<sem::Pointer>())
|
||||
<< "uniform_ptr is " << TypeOf(uniform_ptr)->TypeInfo().name;
|
||||
ASSERT_TRUE(TypeOf(storage_ptr)->Is<sem::Pointer>())
|
||||
<< "storage_ptr is " << TypeOf(storage_ptr)->TypeInfo().name;
|
||||
|
||||
EXPECT_EQ(TypeOf(function_ptr)->As<sem::Pointer>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(private_ptr)->As<sem::Pointer>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(workgroup_ptr)->As<sem::Pointer>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(uniform_ptr)->As<sem::Pointer>()->Access(),
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(TypeOf(storage_ptr)->As<sem::Pointer>()->Access(),
|
||||
ast::Access::kRead);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
176
src/tint/resolver/ptr_ref_validation_test.cc
Normal file
176
src/tint/resolver/ptr_ref_validation_test.cc
Normal file
@@ -0,0 +1,176 @@
|
||||
// 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/tint/ast/bitcast_expression.h"
|
||||
#include "src/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/reference_type.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
struct ResolverPtrRefValidationTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, AddressOfLiteral) {
|
||||
// &1
|
||||
|
||||
auto* expr = AddressOf(Expr(Source{{12, 34}}, 1));
|
||||
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, AddressOfLet) {
|
||||
// let l : i32 = 1;
|
||||
// &l
|
||||
auto* l = Const("l", ty.i32(), Expr(1));
|
||||
auto* expr = AddressOf(Expr(Source{{12, 34}}, "l"));
|
||||
|
||||
WrapInFunction(l, expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(), "12:34 error: cannot take the address of expression");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, AddressOfHandle) {
|
||||
// @group(0) @binding(0) var t: texture_3d<f32>;
|
||||
// &t
|
||||
Global("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
|
||||
GroupAndBinding(0u, 0u));
|
||||
auto* expr = AddressOf(Expr(Source{{12, 34}}, "t"));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot take the address of expression in handle "
|
||||
"storage class");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, AddressOfVectorComponent_MemberAccessor) {
|
||||
// var v : vec4<i32>;
|
||||
// &v.y
|
||||
auto* v = Var("v", ty.vec4<i32>());
|
||||
auto* expr = AddressOf(MemberAccessor(Source{{12, 34}}, "v", "y"));
|
||||
|
||||
WrapInFunction(v, expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot take the address of a vector component");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, AddressOfVectorComponent_IndexAccessor) {
|
||||
// var v : vec4<i32>;
|
||||
// &v[2]
|
||||
auto* v = Var("v", ty.vec4<i32>());
|
||||
auto* expr = AddressOf(IndexAccessor(Source{{12, 34}}, "v", 2));
|
||||
|
||||
WrapInFunction(v, expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot take the address of a vector component");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, IndirectOfAddressOfHandle) {
|
||||
// @group(0) @binding(0) var t: texture_3d<f32>;
|
||||
// *&t
|
||||
Global("t", ty.sampled_texture(ast::TextureDimension::k3d, ty.f32()),
|
||||
GroupAndBinding(0u, 0u));
|
||||
auto* expr = Deref(AddressOf(Expr(Source{{12, 34}}, "t")));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot take the address of expression in handle "
|
||||
"storage class");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, DerefOfLiteral) {
|
||||
// *1
|
||||
|
||||
auto* expr = Deref(Expr(Source{{12, 34}}, 1));
|
||||
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot dereference expression of type 'i32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, DerefOfVar) {
|
||||
// var v : i32 = 1;
|
||||
// *1
|
||||
auto* v = Var("v", ty.i32());
|
||||
auto* expr = Deref(Expr(Source{{12, 34}}, "v"));
|
||||
|
||||
WrapInFunction(v, expr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot dereference expression of type 'i32'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverPtrRefValidationTest, InferredPtrAccessMismatch) {
|
||||
// struct Inner {
|
||||
// arr: array<i32, 4>;
|
||||
// }
|
||||
// [[block]] struct S {
|
||||
// inner: Inner;
|
||||
// }
|
||||
// @group(0) @binding(0) var<storage, read_write> s : S;
|
||||
// fn f() {
|
||||
// let p : pointer<storage, i32> = &s.inner.arr[2];
|
||||
// }
|
||||
auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
|
||||
auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
auto* expr =
|
||||
IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
|
||||
auto* ptr =
|
||||
Const(Source{{12, 34}}, "p", ty.pointer<i32>(ast::StorageClass::kStorage),
|
||||
AddressOf(expr));
|
||||
|
||||
WrapInFunction(ptr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot initialize let of type "
|
||||
"'ptr<storage, i32, read>' with value of type "
|
||||
"'ptr<storage, i32, read_write>'");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
2917
src/tint/resolver/resolver.cc
Normal file
2917
src/tint/resolver/resolver.cc
Normal file
File diff suppressed because it is too large
Load Diff
545
src/tint/resolver/resolver.h
Normal file
545
src/tint/resolver/resolver.h
Normal file
@@ -0,0 +1,545 @@
|
||||
// Copyright 2020 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.
|
||||
|
||||
#ifndef SRC_TINT_RESOLVER_RESOLVER_H_
|
||||
#define SRC_TINT_RESOLVER_RESOLVER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "src/tint/builtin_table.h"
|
||||
#include "src/tint/program_builder.h"
|
||||
#include "src/tint/resolver/dependency_graph.h"
|
||||
#include "src/tint/scope_stack.h"
|
||||
#include "src/tint/sem/binding_point.h"
|
||||
#include "src/tint/sem/block_statement.h"
|
||||
#include "src/tint/sem/constant.h"
|
||||
#include "src/tint/sem/function.h"
|
||||
#include "src/tint/sem/struct.h"
|
||||
#include "src/tint/utils/map.h"
|
||||
#include "src/tint/utils/unique_vector.h"
|
||||
|
||||
namespace tint {
|
||||
|
||||
// Forward declarations
|
||||
namespace ast {
|
||||
class IndexAccessorExpression;
|
||||
class BinaryExpression;
|
||||
class BitcastExpression;
|
||||
class CallExpression;
|
||||
class CallStatement;
|
||||
class CaseStatement;
|
||||
class ForLoopStatement;
|
||||
class Function;
|
||||
class IdentifierExpression;
|
||||
class LoopStatement;
|
||||
class MemberAccessorExpression;
|
||||
class ReturnStatement;
|
||||
class SwitchStatement;
|
||||
class UnaryOpExpression;
|
||||
class Variable;
|
||||
} // namespace ast
|
||||
namespace sem {
|
||||
class Array;
|
||||
class Atomic;
|
||||
class BlockStatement;
|
||||
class Builtin;
|
||||
class CaseStatement;
|
||||
class ElseStatement;
|
||||
class ForLoopStatement;
|
||||
class IfStatement;
|
||||
class LoopStatement;
|
||||
class Statement;
|
||||
class SwitchStatement;
|
||||
class TypeConstructor;
|
||||
} // namespace sem
|
||||
|
||||
namespace resolver {
|
||||
|
||||
/// Resolves types for all items in the given tint program
|
||||
class Resolver {
|
||||
public:
|
||||
/// Constructor
|
||||
/// @param builder the program builder
|
||||
explicit Resolver(ProgramBuilder* builder);
|
||||
|
||||
/// Destructor
|
||||
~Resolver();
|
||||
|
||||
/// @returns error messages from the resolver
|
||||
std::string error() const { return diagnostics_.str(); }
|
||||
|
||||
/// @returns true if the resolver was successful
|
||||
bool Resolve();
|
||||
|
||||
/// @param type the given type
|
||||
/// @returns true if the given type is a plain type
|
||||
bool IsPlain(const sem::Type* type) const;
|
||||
|
||||
/// @param type the given type
|
||||
/// @returns true if the given type is a fixed-footprint type
|
||||
bool IsFixedFootprint(const sem::Type* type) const;
|
||||
|
||||
/// @param type the given type
|
||||
/// @returns true if the given type is storable
|
||||
bool IsStorable(const sem::Type* type) const;
|
||||
|
||||
/// @param type the given type
|
||||
/// @returns true if the given type is host-shareable
|
||||
bool IsHostShareable(const sem::Type* type) const;
|
||||
|
||||
private:
|
||||
/// Describes the context in which a variable is declared
|
||||
enum class VariableKind { kParameter, kLocal, kGlobal };
|
||||
|
||||
std::set<std::pair<const sem::Type*, ast::StorageClass>>
|
||||
valid_type_storage_layouts_;
|
||||
|
||||
/// Structure holding semantic information about a block (i.e. scope), such as
|
||||
/// parent block and variables declared in the block.
|
||||
/// Used to validate variable scoping rules.
|
||||
struct BlockInfo {
|
||||
enum class Type { kGeneric, kLoop, kLoopContinuing, kSwitchCase };
|
||||
|
||||
BlockInfo(const ast::BlockStatement* block, Type type, BlockInfo* parent);
|
||||
~BlockInfo();
|
||||
|
||||
template <typename Pred>
|
||||
BlockInfo* FindFirstParent(Pred&& pred) {
|
||||
BlockInfo* curr = this;
|
||||
while (curr && !pred(curr)) {
|
||||
curr = curr->parent;
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
|
||||
BlockInfo* FindFirstParent(BlockInfo::Type ty) {
|
||||
return FindFirstParent(
|
||||
[ty](auto* block_info) { return block_info->type == ty; });
|
||||
}
|
||||
|
||||
ast::BlockStatement const* const block;
|
||||
const Type type;
|
||||
BlockInfo* const parent;
|
||||
std::vector<const ast::Variable*> decls;
|
||||
|
||||
// first_continue is set to the index of the first variable in decls
|
||||
// declared after the first continue statement in a loop block, if any.
|
||||
constexpr static size_t kNoContinue = size_t(~0);
|
||||
size_t first_continue = kNoContinue;
|
||||
};
|
||||
|
||||
// Structure holding information for a TypeDecl
|
||||
struct TypeDeclInfo {
|
||||
ast::TypeDecl const* const ast;
|
||||
sem::Type* const sem;
|
||||
};
|
||||
|
||||
/// Resolves the program, without creating final the semantic nodes.
|
||||
/// @returns true on success, false on error
|
||||
bool ResolveInternal();
|
||||
|
||||
bool ValidatePipelineStages();
|
||||
|
||||
/// Creates the nodes and adds them to the sem::Info mappings of the
|
||||
/// ProgramBuilder.
|
||||
void CreateSemanticNodes() const;
|
||||
|
||||
/// Retrieves information for the requested import.
|
||||
/// @param src the source of the import
|
||||
/// @param path the import path
|
||||
/// @param name the method name to get information on
|
||||
/// @param params the parameters to the method call
|
||||
/// @param id out parameter for the external call ID. Must not be a nullptr.
|
||||
/// @returns the return type of `name` in `path` or nullptr on error.
|
||||
sem::Type* GetImportData(const Source& src,
|
||||
const std::string& path,
|
||||
const std::string& name,
|
||||
const ast::ExpressionList& params,
|
||||
uint32_t* id);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// AST and Type traversal methods
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Expression resolving methods
|
||||
// Returns the semantic node pointer on success, nullptr on failure.
|
||||
sem::Expression* IndexAccessor(const ast::IndexAccessorExpression*);
|
||||
sem::Expression* Binary(const ast::BinaryExpression*);
|
||||
sem::Expression* Bitcast(const ast::BitcastExpression*);
|
||||
sem::Call* Call(const ast::CallExpression*);
|
||||
sem::Expression* Expression(const ast::Expression*);
|
||||
sem::Function* Function(const ast::Function*);
|
||||
sem::Call* FunctionCall(const ast::CallExpression*,
|
||||
sem::Function* target,
|
||||
const std::vector<const sem::Expression*> args,
|
||||
sem::Behaviors arg_behaviors);
|
||||
sem::Expression* Identifier(const ast::IdentifierExpression*);
|
||||
sem::Call* BuiltinCall(const ast::CallExpression*,
|
||||
sem::BuiltinType,
|
||||
const std::vector<const sem::Expression*> args,
|
||||
const std::vector<const sem::Type*> arg_tys);
|
||||
sem::Expression* Literal(const ast::LiteralExpression*);
|
||||
sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
|
||||
sem::Call* TypeConversion(const ast::CallExpression* expr,
|
||||
const sem::Type* ty,
|
||||
const sem::Expression* arg,
|
||||
const sem::Type* arg_ty);
|
||||
sem::Call* TypeConstructor(const ast::CallExpression* expr,
|
||||
const sem::Type* ty,
|
||||
const std::vector<const sem::Expression*> args,
|
||||
const std::vector<const sem::Type*> arg_tys);
|
||||
sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
|
||||
|
||||
// Statement resolving methods
|
||||
// Each return true on success, false on failure.
|
||||
sem::Statement* AssignmentStatement(const ast::AssignmentStatement*);
|
||||
sem::BlockStatement* BlockStatement(const ast::BlockStatement*);
|
||||
sem::Statement* BreakStatement(const ast::BreakStatement*);
|
||||
sem::Statement* CallStatement(const ast::CallStatement*);
|
||||
sem::CaseStatement* CaseStatement(const ast::CaseStatement*);
|
||||
sem::Statement* ContinueStatement(const ast::ContinueStatement*);
|
||||
sem::Statement* DiscardStatement(const ast::DiscardStatement*);
|
||||
sem::ElseStatement* ElseStatement(const ast::ElseStatement*);
|
||||
sem::Statement* FallthroughStatement(const ast::FallthroughStatement*);
|
||||
sem::ForLoopStatement* ForLoopStatement(const ast::ForLoopStatement*);
|
||||
sem::GlobalVariable* GlobalVariable(const ast::Variable*);
|
||||
sem::Statement* Parameter(const ast::Variable*);
|
||||
sem::IfStatement* IfStatement(const ast::IfStatement*);
|
||||
sem::LoopStatement* LoopStatement(const ast::LoopStatement*);
|
||||
sem::Statement* ReturnStatement(const ast::ReturnStatement*);
|
||||
sem::Statement* Statement(const ast::Statement*);
|
||||
sem::SwitchStatement* SwitchStatement(const ast::SwitchStatement* s);
|
||||
sem::Statement* VariableDeclStatement(const ast::VariableDeclStatement*);
|
||||
bool Statements(const ast::StatementList&);
|
||||
|
||||
// AST and Type validation methods
|
||||
// Each return true on success, false on failure.
|
||||
bool ValidateAlias(const ast::Alias*);
|
||||
bool ValidateArray(const sem::Array* arr, const Source& source);
|
||||
bool ValidateArrayStrideAttribute(const ast::StrideAttribute* attr,
|
||||
uint32_t el_size,
|
||||
uint32_t el_align,
|
||||
const Source& source);
|
||||
bool ValidateAtomic(const ast::Atomic* a, const sem::Atomic* s);
|
||||
bool ValidateAtomicVariable(const sem::Variable* var);
|
||||
bool ValidateAssignment(const ast::AssignmentStatement* a);
|
||||
bool ValidateBitcast(const ast::BitcastExpression* cast, const sem::Type* to);
|
||||
bool ValidateBreakStatement(const sem::Statement* stmt);
|
||||
bool ValidateBuiltinAttribute(const ast::BuiltinAttribute* attr,
|
||||
const sem::Type* storage_type,
|
||||
const bool is_input);
|
||||
bool ValidateContinueStatement(const sem::Statement* stmt);
|
||||
bool ValidateDiscardStatement(const sem::Statement* stmt);
|
||||
bool ValidateElseStatement(const sem::ElseStatement* stmt);
|
||||
bool ValidateEntryPoint(const sem::Function* func);
|
||||
bool ValidateForLoopStatement(const sem::ForLoopStatement* stmt);
|
||||
bool ValidateFallthroughStatement(const sem::Statement* stmt);
|
||||
bool ValidateFunction(const sem::Function* func);
|
||||
bool ValidateFunctionCall(const sem::Call* call);
|
||||
bool ValidateGlobalVariable(const sem::Variable* var);
|
||||
bool ValidateIfStatement(const sem::IfStatement* stmt);
|
||||
bool ValidateInterpolateAttribute(const ast::InterpolateAttribute* attr,
|
||||
const sem::Type* storage_type);
|
||||
bool ValidateBuiltinCall(const sem::Call* call);
|
||||
bool ValidateLocationAttribute(const ast::LocationAttribute* location,
|
||||
const sem::Type* type,
|
||||
std::unordered_set<uint32_t>& locations,
|
||||
const Source& source,
|
||||
const bool is_input = false);
|
||||
bool ValidateLoopStatement(const sem::LoopStatement* stmt);
|
||||
bool ValidateMatrix(const sem::Matrix* ty, const Source& source);
|
||||
bool ValidateFunctionParameter(const ast::Function* func,
|
||||
const sem::Variable* var);
|
||||
bool ValidateParameter(const ast::Function* func, const sem::Variable* var);
|
||||
bool ValidateReturn(const ast::ReturnStatement* ret);
|
||||
bool ValidateStatements(const ast::StatementList& stmts);
|
||||
bool ValidateStorageTexture(const ast::StorageTexture* t);
|
||||
bool ValidateStructure(const sem::Struct* str);
|
||||
bool ValidateStructureConstructorOrCast(const ast::CallExpression* ctor,
|
||||
const sem::Struct* struct_type);
|
||||
bool ValidateSwitch(const ast::SwitchStatement* s);
|
||||
bool ValidateVariable(const sem::Variable* var);
|
||||
bool ValidateVariableConstructorOrCast(const ast::Variable* var,
|
||||
ast::StorageClass storage_class,
|
||||
const sem::Type* storage_type,
|
||||
const sem::Type* rhs_type);
|
||||
bool ValidateVector(const sem::Vector* ty, const Source& source);
|
||||
bool ValidateVectorConstructorOrCast(const ast::CallExpression* ctor,
|
||||
const sem::Vector* vec_type);
|
||||
bool ValidateMatrixConstructorOrCast(const ast::CallExpression* ctor,
|
||||
const sem::Matrix* matrix_type);
|
||||
bool ValidateScalarConstructorOrCast(const ast::CallExpression* ctor,
|
||||
const sem::Type* type);
|
||||
bool ValidateArrayConstructorOrCast(const ast::CallExpression* ctor,
|
||||
const sem::Array* arr_type);
|
||||
bool ValidateTextureBuiltinFunction(const sem::Call* call);
|
||||
bool ValidateNoDuplicateAttributes(const ast::AttributeList& attributes);
|
||||
bool ValidateStorageClassLayout(const sem::Type* type,
|
||||
ast::StorageClass sc,
|
||||
Source source);
|
||||
bool ValidateStorageClassLayout(const sem::Variable* var);
|
||||
|
||||
/// @returns true if the attribute list contains a
|
||||
/// ast::DisableValidationAttribute with the validation mode equal to
|
||||
/// `validation`
|
||||
bool IsValidationDisabled(const ast::AttributeList& attributes,
|
||||
ast::DisabledValidation validation) const;
|
||||
|
||||
/// @returns true if the attribute list does not contains a
|
||||
/// ast::DisableValidationAttribute with the validation mode equal to
|
||||
/// `validation`
|
||||
bool IsValidationEnabled(const ast::AttributeList& attributes,
|
||||
ast::DisabledValidation validation) const;
|
||||
|
||||
/// Resolves the WorkgroupSize for the given function, assigning it to
|
||||
/// current_function_
|
||||
bool WorkgroupSize(const ast::Function*);
|
||||
|
||||
/// @returns the sem::Type for the ast::Type `ty`, building it if it
|
||||
/// hasn't been constructed already. If an error is raised, nullptr is
|
||||
/// returned.
|
||||
/// @param ty the ast::Type
|
||||
sem::Type* Type(const ast::Type* ty);
|
||||
|
||||
/// @param named_type the named type to resolve
|
||||
/// @returns the resolved semantic type
|
||||
sem::Type* TypeDecl(const ast::TypeDecl* named_type);
|
||||
|
||||
/// Builds and returns the semantic information for the array `arr`.
|
||||
/// This method does not mark the ast::Array node, nor attach the generated
|
||||
/// semantic information to the AST node.
|
||||
/// @returns the semantic Array information, or nullptr if an error is
|
||||
/// raised.
|
||||
/// @param arr the Array to get semantic information for
|
||||
sem::Array* Array(const ast::Array* arr);
|
||||
|
||||
/// Builds and returns the semantic information for the alias `alias`.
|
||||
/// This method does not mark the ast::Alias node, nor attach the generated
|
||||
/// semantic information to the AST node.
|
||||
/// @returns the aliased type, or nullptr if an error is raised.
|
||||
sem::Type* Alias(const ast::Alias* alias);
|
||||
|
||||
/// Builds and returns the semantic information for the structure `str`.
|
||||
/// This method does not mark the ast::Struct node, nor attach the generated
|
||||
/// semantic information to the AST node.
|
||||
/// @returns the semantic Struct information, or nullptr if an error is
|
||||
/// raised.
|
||||
sem::Struct* Structure(const ast::Struct* str);
|
||||
|
||||
/// @returns the semantic info for the variable `var`. If an error is
|
||||
/// raised, nullptr is returned.
|
||||
/// @note this method does not resolve the attributes as these are
|
||||
/// context-dependent (global, local, parameter)
|
||||
/// @param var the variable to create or return the `VariableInfo` for
|
||||
/// @param kind what kind of variable we are declaring
|
||||
/// @param index the index of the parameter, if this variable is a parameter
|
||||
sem::Variable* Variable(const ast::Variable* var,
|
||||
VariableKind kind,
|
||||
uint32_t index = 0);
|
||||
|
||||
/// Records the storage class usage for the given type, and any transient
|
||||
/// dependencies of the type. Validates that the type can be used for the
|
||||
/// given storage class, erroring if it cannot.
|
||||
/// @param sc the storage class to apply to the type and transitent types
|
||||
/// @param ty the type to apply the storage class on
|
||||
/// @param usage the Source of the root variable declaration that uses the
|
||||
/// given type and storage class. Used for generating sensible error
|
||||
/// messages.
|
||||
/// @returns true on success, false on error
|
||||
bool ApplyStorageClassUsageToType(ast::StorageClass sc,
|
||||
sem::Type* ty,
|
||||
const Source& usage);
|
||||
|
||||
/// @param storage_class the storage class
|
||||
/// @returns the default access control for the given storage class
|
||||
ast::Access DefaultAccessForStorageClass(ast::StorageClass storage_class);
|
||||
|
||||
/// Allocate constant IDs for pipeline-overridable constants.
|
||||
void AllocateOverridableConstantIds();
|
||||
|
||||
/// Set the shadowing information on variable declarations.
|
||||
/// @note this method must only be called after all semantic nodes are built.
|
||||
void SetShadows();
|
||||
|
||||
/// @returns the resolved type of the ast::Expression `expr`
|
||||
/// @param expr the expression
|
||||
sem::Type* TypeOf(const ast::Expression* expr);
|
||||
|
||||
/// @returns the type name of the given semantic type, unwrapping
|
||||
/// references.
|
||||
std::string TypeNameOf(const sem::Type* ty);
|
||||
|
||||
/// @returns the type name of the given semantic type, without unwrapping
|
||||
/// references.
|
||||
std::string RawTypeNameOf(const sem::Type* ty);
|
||||
|
||||
/// @returns the semantic type of the AST literal `lit`
|
||||
/// @param lit the literal
|
||||
sem::Type* TypeOf(const ast::LiteralExpression* lit);
|
||||
|
||||
/// StatementScope() does the following:
|
||||
/// * Creates the AST -> SEM mapping.
|
||||
/// * Assigns `sem` to #current_statement_
|
||||
/// * Assigns `sem` to #current_compound_statement_ if `sem` derives from
|
||||
/// sem::CompoundStatement.
|
||||
/// * Assigns `sem` to #current_block_ if `sem` derives from
|
||||
/// sem::BlockStatement.
|
||||
/// * Then calls `callback`.
|
||||
/// * Before returning #current_statement_, #current_compound_statement_, and
|
||||
/// #current_block_ are restored to their original values.
|
||||
/// @returns `sem` if `callback` returns true, otherwise `nullptr`.
|
||||
template <typename SEM, typename F>
|
||||
SEM* StatementScope(const ast::Statement* ast, SEM* sem, F&& callback);
|
||||
|
||||
/// Returns a human-readable string representation of the vector type name
|
||||
/// with the given parameters.
|
||||
/// @param size the vector dimension
|
||||
/// @param element_type scalar vector sub-element type
|
||||
/// @return pretty string representation
|
||||
std::string VectorPretty(uint32_t size, const sem::Type* element_type);
|
||||
|
||||
/// Mark records that the given AST node has been visited, and asserts that
|
||||
/// the given node has not already been seen. Diamonds in the AST are
|
||||
/// illegal.
|
||||
/// @param node the AST node.
|
||||
/// @returns true on success, false on error
|
||||
bool Mark(const ast::Node* node);
|
||||
|
||||
/// Adds the given error message to the diagnostics
|
||||
void AddError(const std::string& msg, const Source& source) const;
|
||||
|
||||
/// Adds the given warning message to the diagnostics
|
||||
void AddWarning(const std::string& msg, const Source& source) const;
|
||||
|
||||
/// Adds the given note message to the diagnostics
|
||||
void AddNote(const std::string& msg, const Source& source) const;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// Constant value evaluation methods
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/// Cast `Value` to `target_type`
|
||||
/// @return the casted value
|
||||
sem::Constant ConstantCast(const sem::Constant& value,
|
||||
const sem::Type* target_elem_type);
|
||||
|
||||
sem::Constant EvaluateConstantValue(const ast::Expression* expr,
|
||||
const sem::Type* type);
|
||||
sem::Constant EvaluateConstantValue(const ast::LiteralExpression* literal,
|
||||
const sem::Type* type);
|
||||
sem::Constant EvaluateConstantValue(const ast::CallExpression* call,
|
||||
const sem::Type* type);
|
||||
|
||||
/// Sem is a helper for obtaining the semantic node for the given AST node.
|
||||
template <typename SEM = sem::Info::InferFromAST,
|
||||
typename AST_OR_TYPE = CastableBase>
|
||||
auto* Sem(const AST_OR_TYPE* ast) {
|
||||
using T = sem::Info::GetResultType<SEM, AST_OR_TYPE>;
|
||||
auto* sem = builder_->Sem().Get(ast);
|
||||
if (!sem) {
|
||||
TINT_ICE(Resolver, diagnostics_)
|
||||
<< "AST node '" << ast->TypeInfo().name << "' had no semantic info\n"
|
||||
<< "At: " << ast->source << "\n"
|
||||
<< "Pointer: " << ast;
|
||||
}
|
||||
return const_cast<T*>(As<T>(sem));
|
||||
}
|
||||
|
||||
/// @returns true if the symbol is the name of a builtin function.
|
||||
bool IsBuiltin(Symbol) const;
|
||||
|
||||
/// @returns true if `expr` is the current CallStatement's CallExpression
|
||||
bool IsCallStatement(const ast::Expression* expr) const;
|
||||
|
||||
/// Searches the current statement and up through parents of the current
|
||||
/// statement looking for a loop or for-loop continuing statement.
|
||||
/// @returns the closest continuing statement to the current statement that
|
||||
/// (transitively) owns the current statement.
|
||||
/// @param stop_at_loop if true then the function will return nullptr if a
|
||||
/// loop or for-loop was found before the continuing.
|
||||
const ast::Statement* ClosestContinuing(bool stop_at_loop) const;
|
||||
|
||||
/// @returns the resolved symbol (function, type or variable) for the given
|
||||
/// ast::Identifier or ast::TypeName cast to the given semantic type.
|
||||
template <typename SEM = sem::Node>
|
||||
SEM* ResolvedSymbol(const ast::Node* node) {
|
||||
auto* resolved = utils::Lookup(dependencies_.resolved_symbols, node);
|
||||
return resolved ? const_cast<SEM*>(builder_->Sem().Get<SEM>(resolved))
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
struct TypeConversionSig {
|
||||
const sem::Type* target;
|
||||
const sem::Type* source;
|
||||
|
||||
bool operator==(const TypeConversionSig&) const;
|
||||
|
||||
/// Hasher provides a hash function for the TypeConversionSig
|
||||
struct Hasher {
|
||||
/// @param sig the TypeConversionSig to create a hash for
|
||||
/// @return the hash value
|
||||
std::size_t operator()(const TypeConversionSig& sig) const;
|
||||
};
|
||||
};
|
||||
|
||||
struct TypeConstructorSig {
|
||||
const sem::Type* type;
|
||||
const std::vector<const sem::Type*> parameters;
|
||||
|
||||
TypeConstructorSig(const sem::Type* ty,
|
||||
const std::vector<const sem::Type*> params);
|
||||
TypeConstructorSig(const TypeConstructorSig&);
|
||||
~TypeConstructorSig();
|
||||
bool operator==(const TypeConstructorSig&) const;
|
||||
|
||||
/// Hasher provides a hash function for the TypeConstructorSig
|
||||
struct Hasher {
|
||||
/// @param sig the TypeConstructorSig to create a hash for
|
||||
/// @return the hash value
|
||||
std::size_t operator()(const TypeConstructorSig& sig) const;
|
||||
};
|
||||
};
|
||||
|
||||
ProgramBuilder* const builder_;
|
||||
diag::List& diagnostics_;
|
||||
std::unique_ptr<BuiltinTable> const builtin_table_;
|
||||
DependencyGraph dependencies_;
|
||||
std::vector<sem::Function*> entry_points_;
|
||||
std::unordered_map<const sem::Type*, const Source&> atomic_composite_info_;
|
||||
std::unordered_set<const ast::Node*> marked_;
|
||||
std::unordered_map<uint32_t, const sem::Variable*> constant_ids_;
|
||||
std::unordered_map<TypeConversionSig,
|
||||
sem::CallTarget*,
|
||||
TypeConversionSig::Hasher>
|
||||
type_conversions_;
|
||||
std::unordered_map<TypeConstructorSig,
|
||||
sem::CallTarget*,
|
||||
TypeConstructorSig::Hasher>
|
||||
type_ctors_;
|
||||
|
||||
sem::Function* current_function_ = nullptr;
|
||||
sem::Statement* current_statement_ = nullptr;
|
||||
sem::CompoundStatement* current_compound_statement_ = nullptr;
|
||||
sem::BlockStatement* current_block_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_TINT_RESOLVER_RESOLVER_H_
|
||||
659
src/tint/resolver/resolver_behavior_test.cc
Normal file
659
src/tint/resolver/resolver_behavior_test.cc
Normal file
@@ -0,0 +1,659 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/expression.h"
|
||||
#include "src/tint/sem/for_loop_statement.h"
|
||||
#include "src/tint/sem/if_statement.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
class ResolverBehaviorTest : public ResolverTest {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
// Create a function called 'DiscardOrNext' which returns an i32, and has
|
||||
// the behavior of {Discard, Return}, which when called, will have the
|
||||
// behavior {Discard, Next}.
|
||||
Func("DiscardOrNext", {}, ty.i32(),
|
||||
{
|
||||
If(true, Block(Discard())),
|
||||
Return(1),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ResolverBehaviorTest, ExprBinaryOp_LHS) {
|
||||
auto* stmt = Decl(Var("lhs", ty.i32(), Add(Call("DiscardOrNext"), 1)));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, ExprBinaryOp_RHS) {
|
||||
auto* stmt = Decl(Var("lhs", ty.i32(), Add(1, Call("DiscardOrNext"))));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, ExprBitcastOp) {
|
||||
auto* stmt = Decl(Var("lhs", ty.u32(), Bitcast<u32>(Call("DiscardOrNext"))));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, ExprIndex_Arr) {
|
||||
Func("ArrayDiscardOrNext", {}, ty.array<i32, 4>(),
|
||||
{
|
||||
If(true, Block(Discard())),
|
||||
Return(Construct(ty.array<i32, 4>())),
|
||||
});
|
||||
|
||||
auto* stmt =
|
||||
Decl(Var("lhs", ty.i32(), IndexAccessor(Call("ArrayDiscardOrNext"), 1)));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, ExprIndex_Idx) {
|
||||
auto* stmt =
|
||||
Decl(Var("lhs", ty.i32(), IndexAccessor("arr", Call("DiscardOrNext"))));
|
||||
WrapInFunction(Decl(Var("arr", ty.array<i32, 4>())), //
|
||||
stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, ExprUnaryOp) {
|
||||
auto* stmt = Decl(Var("lhs", ty.i32(),
|
||||
create<ast::UnaryOpExpression>(
|
||||
ast::UnaryOp::kComplement, Call("DiscardOrNext"))));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtAssign) {
|
||||
auto* stmt = Assign("lhs", "rhs");
|
||||
WrapInFunction(Decl(Var("lhs", ty.i32())), //
|
||||
Decl(Var("rhs", ty.i32())), //
|
||||
stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtAssign_LHSDiscardOrNext) {
|
||||
auto* stmt = Assign(IndexAccessor("lhs", Call("DiscardOrNext")), 1);
|
||||
WrapInFunction(Decl(Var("lhs", ty.array<i32, 4>())), //
|
||||
stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtAssign_RHSDiscardOrNext) {
|
||||
auto* stmt = Assign("lhs", Call("DiscardOrNext"));
|
||||
WrapInFunction(Decl(Var("lhs", ty.i32())), //
|
||||
stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtBlockEmpty) {
|
||||
auto* stmt = Block();
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtBlockSingleStmt) {
|
||||
auto* stmt = Block(Discard());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtCallReturn) {
|
||||
Func("f", {}, ty.void_(), {Return()});
|
||||
auto* stmt = CallStmt(Call("f"));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtCallFuncDiscard) {
|
||||
Func("f", {}, ty.void_(), {Discard()});
|
||||
auto* stmt = CallStmt(Call("f"));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtCallFuncMayDiscard) {
|
||||
auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
|
||||
nullptr, Block(Break()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtBreak) {
|
||||
auto* stmt = Break();
|
||||
WrapInFunction(Loop(Block(stmt)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kBreak);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtContinue) {
|
||||
auto* stmt = Continue();
|
||||
WrapInFunction(Loop(Block(If(true, Block(Break())), //
|
||||
stmt)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kContinue);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtDiscard) {
|
||||
auto* stmt = Discard();
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_NoExit) {
|
||||
auto* stmt = For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopBreak) {
|
||||
auto* stmt = For(nullptr, nullptr, nullptr, Block(Break()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopContinue_NoExit) {
|
||||
auto* stmt =
|
||||
For(Source{{12, 34}}, nullptr, nullptr, nullptr, Block(Continue()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: for-loop does not exit");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopDiscard) {
|
||||
auto* stmt = For(nullptr, nullptr, nullptr, Block(Discard()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopReturn) {
|
||||
auto* stmt = For(nullptr, nullptr, nullptr, Block(Return()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopBreak_InitCallFuncMayDiscard) {
|
||||
auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
|
||||
nullptr, Block(Break()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_InitCallFuncMayDiscard) {
|
||||
auto* stmt = For(Decl(Var("v", ty.i32(), Call("DiscardOrNext"))), nullptr,
|
||||
nullptr, Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondTrue) {
|
||||
auto* stmt = For(nullptr, true, nullptr, Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behaviors(sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtForLoopEmpty_CondCallFuncMayDiscard) {
|
||||
auto* stmt = For(nullptr, Equal(Call("DiscardOrNext"), 1), nullptr, Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock) {
|
||||
auto* stmt = If(true, Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard) {
|
||||
auto* stmt = If(true, Block(Discard()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseDiscard) {
|
||||
auto* stmt = If(true, Block(), Else(Block(Discard())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenDiscard_ElseDiscard) {
|
||||
auto* stmt = If(true, Block(Discard()), Else(Block(Discard())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtIfCallFuncMayDiscard_ThenEmptyBlock) {
|
||||
auto* stmt = If(Equal(Call("DiscardOrNext"), 1), Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtIfTrue_ThenEmptyBlock_ElseCallFuncMayDiscard) {
|
||||
auto* stmt = If(true, Block(), //
|
||||
Else(Equal(Call("DiscardOrNext"), 1), Block()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLetDecl) {
|
||||
auto* stmt = Decl(Const("v", ty.i32(), Expr(1)));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLetDecl_RHSDiscardOrNext) {
|
||||
auto* stmt = Decl(Const("lhs", ty.i32(), Call("DiscardOrNext")));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLoopEmpty_NoExit) {
|
||||
auto* stmt = Loop(Source{{12, 34}}, Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLoopBreak) {
|
||||
auto* stmt = Loop(Block(Break()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLoopContinue_NoExit) {
|
||||
auto* stmt = Loop(Source{{12, 34}}, Block(Continue()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLoopDiscard) {
|
||||
auto* stmt = Loop(Block(Discard()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLoopReturn) {
|
||||
auto* stmt = Loop(Block(Return()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContEmpty_NoExit) {
|
||||
auto* stmt = Loop(Source{{12, 34}}, Block(), Block());
|
||||
WrapInFunction(stmt);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: loop does not exit");
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtLoopEmpty_ContIfTrueBreak) {
|
||||
auto* stmt = Loop(Block(), Block(If(true, Block(Break()))));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtReturn) {
|
||||
auto* stmt = Return();
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtReturn_DiscardOrNext) {
|
||||
auto* stmt = Return(Call("DiscardOrNext"));
|
||||
Func("F", {}, ty.i32(), {stmt});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kReturn, sem::Behavior::kDiscard));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondTrue_DefaultEmpty) {
|
||||
auto* stmt = Switch(1, DefaultCase(Block()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultEmpty) {
|
||||
auto* stmt = Switch(1, DefaultCase(Block()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultDiscard) {
|
||||
auto* stmt = Switch(1, DefaultCase(Block(Discard())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_DefaultReturn) {
|
||||
auto* stmt = Switch(1, DefaultCase(Block(Return())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kReturn);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultEmpty) {
|
||||
auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultDiscard) {
|
||||
auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block(Discard())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kDiscard));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Empty_DefaultReturn) {
|
||||
auto* stmt = Switch(1, Case(Expr(0), Block()), DefaultCase(Block(Return())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kNext, sem::Behavior::kReturn));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondLiteral_Case0Discard_DefaultEmpty) {
|
||||
auto* stmt = Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest,
|
||||
StmtSwitch_CondLiteral_Case0Discard_DefaultDiscard) {
|
||||
auto* stmt =
|
||||
Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block(Discard())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kDiscard);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest,
|
||||
StmtSwitch_CondLiteral_Case0Discard_DefaultReturn) {
|
||||
auto* stmt =
|
||||
Switch(1, Case(Expr(0), Block(Discard())), DefaultCase(Block(Return())));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kReturn));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest,
|
||||
StmtSwitch_CondLiteral_Case0Discard_Case1Return_DefaultEmpty) {
|
||||
auto* stmt = Switch(1, //
|
||||
Case(Expr(0), Block(Discard())), //
|
||||
Case(Expr(1), Block(Return())), //
|
||||
DefaultCase(Block()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext,
|
||||
sem::Behavior::kReturn));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtSwitch_CondCallFuncMayDiscard_DefaultEmpty) {
|
||||
auto* stmt = Switch(Call("DiscardOrNext"), DefaultCase(Block()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtVarDecl) {
|
||||
auto* stmt = Decl(Var("v", ty.i32()));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(), sem::Behavior::kNext);
|
||||
}
|
||||
|
||||
TEST_F(ResolverBehaviorTest, StmtVarDecl_RHSDiscardOrNext) {
|
||||
auto* stmt = Decl(Var("lhs", ty.i32(), Call("DiscardOrNext")));
|
||||
WrapInFunction(stmt);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(stmt);
|
||||
EXPECT_EQ(sem->Behaviors(),
|
||||
sem::Behaviors(sem::Behavior::kDiscard, sem::Behavior::kNext));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
144
src/tint/resolver/resolver_constants.cc
Normal file
144
src/tint/resolver/resolver_constants.cc
Normal file
@@ -0,0 +1,144 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "src/tint/sem/constant.h"
|
||||
#include "src/tint/sem/type_constructor.h"
|
||||
#include "src/tint/utils/map.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using i32 = ProgramBuilder::i32;
|
||||
using u32 = ProgramBuilder::u32;
|
||||
using f32 = ProgramBuilder::f32;
|
||||
|
||||
} // namespace
|
||||
|
||||
sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr,
|
||||
const sem::Type* type) {
|
||||
if (auto* e = expr->As<ast::LiteralExpression>()) {
|
||||
return EvaluateConstantValue(e, type);
|
||||
}
|
||||
if (auto* e = expr->As<ast::CallExpression>()) {
|
||||
return EvaluateConstantValue(e, type);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
sem::Constant Resolver::EvaluateConstantValue(
|
||||
const ast::LiteralExpression* literal,
|
||||
const sem::Type* type) {
|
||||
if (auto* lit = literal->As<ast::SintLiteralExpression>()) {
|
||||
return {type, {lit->ValueAsI32()}};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::UintLiteralExpression>()) {
|
||||
return {type, {lit->ValueAsU32()}};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::FloatLiteralExpression>()) {
|
||||
return {type, {lit->value}};
|
||||
}
|
||||
if (auto* lit = literal->As<ast::BoolLiteralExpression>()) {
|
||||
return {type, {lit->value}};
|
||||
}
|
||||
TINT_UNREACHABLE(Resolver, builder_->Diagnostics());
|
||||
return {};
|
||||
}
|
||||
|
||||
sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call,
|
||||
const sem::Type* type) {
|
||||
auto* vec = type->As<sem::Vector>();
|
||||
|
||||
// For now, only fold scalars and vectors
|
||||
if (!type->is_scalar() && !vec) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* elem_type = vec ? vec->type() : type;
|
||||
int result_size = vec ? static_cast<int>(vec->Width()) : 1;
|
||||
|
||||
// For zero value init, return 0s
|
||||
if (call->args.empty()) {
|
||||
if (elem_type->Is<sem::I32>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, 0));
|
||||
}
|
||||
if (elem_type->Is<sem::U32>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, 0u));
|
||||
}
|
||||
if (elem_type->Is<sem::F32>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, 0.f));
|
||||
}
|
||||
if (elem_type->Is<sem::Bool>()) {
|
||||
return sem::Constant(type, sem::Constant::Scalars(result_size, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Build value for type_ctor from each child value by casting to
|
||||
// type_ctor's type.
|
||||
sem::Constant::Scalars elems;
|
||||
for (auto* expr : call->args) {
|
||||
auto* arg = builder_->Sem().Get(expr);
|
||||
if (!arg || !arg->ConstantValue()) {
|
||||
return {};
|
||||
}
|
||||
auto cast = ConstantCast(arg->ConstantValue(), elem_type);
|
||||
elems.insert(elems.end(), cast.Elements().begin(), cast.Elements().end());
|
||||
}
|
||||
|
||||
// Splat single-value initializers
|
||||
if (elems.size() == 1) {
|
||||
for (int i = 0; i < result_size - 1; ++i) {
|
||||
elems.emplace_back(elems[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return sem::Constant(type, std::move(elems));
|
||||
}
|
||||
|
||||
sem::Constant Resolver::ConstantCast(const sem::Constant& value,
|
||||
const sem::Type* target_elem_type) {
|
||||
if (value.ElementType() == target_elem_type) {
|
||||
return value;
|
||||
}
|
||||
|
||||
sem::Constant::Scalars elems;
|
||||
for (size_t i = 0; i < value.Elements().size(); ++i) {
|
||||
if (target_elem_type->Is<sem::I32>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<i32>(s); }));
|
||||
} else if (target_elem_type->Is<sem::U32>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<u32>(s); }));
|
||||
} else if (target_elem_type->Is<sem::F32>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<f32>(s); }));
|
||||
} else if (target_elem_type->Is<sem::Bool>()) {
|
||||
elems.emplace_back(
|
||||
value.WithScalarAt(i, [](auto&& s) { return static_cast<bool>(s); }));
|
||||
}
|
||||
}
|
||||
|
||||
auto* target_type =
|
||||
value.Type()->Is<sem::Vector>()
|
||||
? builder_->create<sem::Vector>(target_elem_type,
|
||||
static_cast<uint32_t>(elems.size()))
|
||||
: target_elem_type;
|
||||
|
||||
return sem::Constant(target_type, elems);
|
||||
}
|
||||
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
433
src/tint/resolver/resolver_constants_test.cc
Normal file
433
src/tint/resolver/resolver_constants_test.cc
Normal file
@@ -0,0 +1,433 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/expression.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using Scalar = sem::Constant::Scalar;
|
||||
|
||||
using ResolverConstantsTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_i32) {
|
||||
auto* expr = Expr(99);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_u32) {
|
||||
auto* expr = Expr(99u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_f32) {
|
||||
auto* expr = Expr(9.9f);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Scalar_bool) {
|
||||
auto* expr = Expr(true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_EQ(sem->ConstantValue().ElementType(), sem->Type());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_i32) {
|
||||
auto* expr = vec3<i32>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 0);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 0);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 0);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_u32) {
|
||||
auto* expr = vec3<u32>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 0u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_f32) {
|
||||
auto* expr = vec3<f32>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 0u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 0u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_ZeroInit_bool) {
|
||||
auto* expr = vec3<bool>();
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, false);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_i32) {
|
||||
auto* expr = vec3<i32>(99);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 99);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 99);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 99);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_u32) {
|
||||
auto* expr = vec3<u32>(99u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 99u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 99u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 99u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_f32) {
|
||||
auto* expr = vec3<f32>(9.9f);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 9.9f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 9.9f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 9.9f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Splat_bool) {
|
||||
auto* expr = vec3<bool>(true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_i32) {
|
||||
auto* expr = vec3<i32>(1, 2, 3);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_u32) {
|
||||
auto* expr = vec3<u32>(1u, 2u, 3u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_f32) {
|
||||
auto* expr = vec3<f32>(1.f, 2.f, 3.f);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_FullConstruct_bool) {
|
||||
auto* expr = vec3<bool>(true, false, true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_i32) {
|
||||
auto* expr = vec3<i32>(1, vec2<i32>(2, 3));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_u32) {
|
||||
auto* expr = vec3<u32>(vec2<u32>(1u, 2u), 3u);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::U32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::U32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].u32, 1u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].u32, 2u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].u32, 3u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_f32) {
|
||||
auto* expr = vec3<f32>(1.f, vec2<f32>(2.f, 3.f));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 1.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 2.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 3.f);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_MixConstruct_bool) {
|
||||
auto* expr = vec3<bool>(vec2<bool>(true, false), true);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::Bool>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::Bool>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].bool_, true);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].bool_, false);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].bool_, true);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Cast_f32_to_32) {
|
||||
auto* expr = vec3<i32>(vec3<f32>(1.1f, 2.2f, 3.3f));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::I32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].i32, 1);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].i32, 2);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].i32, 3);
|
||||
}
|
||||
|
||||
TEST_F(ResolverConstantsTest, Vec3_Cast_u32_to_f32) {
|
||||
auto* expr = vec3<f32>(vec3<u32>(10u, 20u, 30u));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_NE(sem, nullptr);
|
||||
ASSERT_TRUE(sem->Type()->Is<sem::Vector>());
|
||||
EXPECT_TRUE(sem->Type()->As<sem::Vector>()->type()->Is<sem::F32>());
|
||||
EXPECT_EQ(sem->Type()->As<sem::Vector>()->Width(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
|
||||
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
|
||||
ASSERT_EQ(sem->ConstantValue().Elements().size(), 3u);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[0].f32, 10.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[1].f32, 20.f);
|
||||
EXPECT_EQ(sem->ConstantValue().Elements()[2].f32, 30.f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
2189
src/tint/resolver/resolver_test.cc
Normal file
2189
src/tint/resolver/resolver_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
27
src/tint/resolver/resolver_test_helper.cc
Normal file
27
src/tint/resolver/resolver_test_helper.cc
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
|
||||
TestHelper::TestHelper() : resolver_(std::make_unique<Resolver>(this)) {}
|
||||
|
||||
TestHelper::~TestHelper() = default;
|
||||
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
489
src/tint/resolver/resolver_test_helper.h
Normal file
489
src/tint/resolver/resolver_test_helper.h
Normal file
@@ -0,0 +1,489 @@
|
||||
// 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.
|
||||
|
||||
#ifndef SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_
|
||||
#define SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/tint/program_builder.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/sem/expression.h"
|
||||
#include "src/tint/sem/statement.h"
|
||||
#include "src/tint/sem/variable.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
|
||||
/// Helper class for testing
|
||||
class TestHelper : public ProgramBuilder {
|
||||
public:
|
||||
/// Constructor
|
||||
TestHelper();
|
||||
|
||||
/// Destructor
|
||||
~TestHelper() override;
|
||||
|
||||
/// @return a pointer to the Resolver
|
||||
Resolver* r() const { return resolver_.get(); }
|
||||
|
||||
/// Returns the statement that holds the given expression.
|
||||
/// @param expr the ast::Expression
|
||||
/// @return the ast::Statement of the ast::Expression, or nullptr if the
|
||||
/// expression is not owned by a statement.
|
||||
const ast::Statement* StmtOf(const ast::Expression* expr) {
|
||||
auto* sem_stmt = Sem().Get(expr)->Stmt();
|
||||
return sem_stmt ? sem_stmt->Declaration() : nullptr;
|
||||
}
|
||||
|
||||
/// Returns the BlockStatement that holds the given statement.
|
||||
/// @param stmt the ast::Statement
|
||||
/// @return the ast::BlockStatement that holds the ast::Statement, or nullptr
|
||||
/// if the statement is not owned by a BlockStatement.
|
||||
const ast::BlockStatement* BlockOf(const ast::Statement* stmt) {
|
||||
auto* sem_stmt = Sem().Get(stmt);
|
||||
return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
|
||||
}
|
||||
|
||||
/// Returns the BlockStatement that holds the given expression.
|
||||
/// @param expr the ast::Expression
|
||||
/// @return the ast::Statement of the ast::Expression, or nullptr if the
|
||||
/// expression is not indirectly owned by a BlockStatement.
|
||||
const ast::BlockStatement* BlockOf(const ast::Expression* expr) {
|
||||
auto* sem_stmt = Sem().Get(expr)->Stmt();
|
||||
return sem_stmt ? sem_stmt->Block()->Declaration() : nullptr;
|
||||
}
|
||||
|
||||
/// Returns the semantic variable for the given identifier expression.
|
||||
/// @param expr the identifier expression
|
||||
/// @return the resolved sem::Variable of the identifier, or nullptr if
|
||||
/// the expression did not resolve to a variable.
|
||||
const sem::Variable* VarOf(const ast::Expression* expr) {
|
||||
auto* sem_ident = Sem().Get(expr);
|
||||
auto* var_user = sem_ident ? sem_ident->As<sem::VariableUser>() : nullptr;
|
||||
return var_user ? var_user->Variable() : nullptr;
|
||||
}
|
||||
|
||||
/// Checks that all the users of the given variable are as expected
|
||||
/// @param var the variable to check
|
||||
/// @param expected_users the expected users of the variable
|
||||
/// @return true if all users are as expected
|
||||
bool CheckVarUsers(const ast::Variable* var,
|
||||
std::vector<const ast::Expression*>&& expected_users) {
|
||||
auto& var_users = Sem().Get(var)->Users();
|
||||
if (var_users.size() != expected_users.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < var_users.size(); i++) {
|
||||
if (var_users[i]->Declaration() != expected_users[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @param type a type
|
||||
/// @returns the name for `type` that closely resembles how it would be
|
||||
/// declared in WGSL.
|
||||
std::string FriendlyName(const ast::Type* type) {
|
||||
return type->FriendlyName(Symbols());
|
||||
}
|
||||
|
||||
/// @param type a type
|
||||
/// @returns the name for `type` that closely resembles how it would be
|
||||
/// declared in WGSL.
|
||||
std::string FriendlyName(const sem::Type* type) {
|
||||
return type->FriendlyName(Symbols());
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Resolver> resolver_;
|
||||
};
|
||||
|
||||
class ResolverTest : public TestHelper, public testing::Test {};
|
||||
|
||||
template <typename T>
|
||||
class ResolverTestWithParam : public TestHelper,
|
||||
public testing::TestWithParam<T> {};
|
||||
|
||||
namespace builder {
|
||||
|
||||
using i32 = ProgramBuilder::i32;
|
||||
using u32 = ProgramBuilder::u32;
|
||||
using f32 = ProgramBuilder::f32;
|
||||
|
||||
template <int N, typename T>
|
||||
struct vec {};
|
||||
|
||||
template <typename T>
|
||||
using vec2 = vec<2, T>;
|
||||
|
||||
template <typename T>
|
||||
using vec3 = vec<3, T>;
|
||||
|
||||
template <typename T>
|
||||
using vec4 = vec<4, T>;
|
||||
|
||||
template <int N, int M, typename T>
|
||||
struct mat {};
|
||||
|
||||
template <typename T>
|
||||
using mat2x2 = mat<2, 2, T>;
|
||||
|
||||
template <typename T>
|
||||
using mat2x3 = mat<2, 3, T>;
|
||||
|
||||
template <typename T>
|
||||
using mat3x2 = mat<3, 2, T>;
|
||||
|
||||
template <typename T>
|
||||
using mat3x3 = mat<3, 3, T>;
|
||||
|
||||
template <typename T>
|
||||
using mat4x4 = mat<4, 4, T>;
|
||||
|
||||
template <int N, typename T>
|
||||
struct array {};
|
||||
|
||||
template <typename TO, int ID = 0>
|
||||
struct alias {};
|
||||
|
||||
template <typename TO>
|
||||
using alias1 = alias<TO, 1>;
|
||||
|
||||
template <typename TO>
|
||||
using alias2 = alias<TO, 2>;
|
||||
|
||||
template <typename TO>
|
||||
using alias3 = alias<TO, 3>;
|
||||
|
||||
template <typename TO>
|
||||
struct ptr {};
|
||||
|
||||
using ast_type_func_ptr = const ast::Type* (*)(ProgramBuilder& b);
|
||||
using ast_expr_func_ptr = const ast::Expression* (*)(ProgramBuilder& b,
|
||||
int elem_value);
|
||||
using sem_type_func_ptr = const sem::Type* (*)(ProgramBuilder& b);
|
||||
|
||||
template <typename T>
|
||||
struct DataType {};
|
||||
|
||||
/// Helper for building bool types and expressions
|
||||
template <>
|
||||
struct DataType<bool> {
|
||||
/// false as bool is not a composite type
|
||||
static constexpr bool is_composite = false;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST bool type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.bool_(); }
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic bool type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
return b.create<sem::Bool>();
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the b
|
||||
/// @return a new AST expression of the bool type
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
|
||||
return b.Expr(elem_value == 0);
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building i32 types and expressions
|
||||
template <>
|
||||
struct DataType<i32> {
|
||||
/// false as i32 is not a composite type
|
||||
static constexpr bool is_composite = false;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST i32 type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.i32(); }
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic i32 type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
return b.create<sem::I32>();
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value i32 will be initialized with
|
||||
/// @return a new AST i32 literal value expression
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
|
||||
return b.Expr(static_cast<i32>(elem_value));
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building u32 types and expressions
|
||||
template <>
|
||||
struct DataType<u32> {
|
||||
/// false as u32 is not a composite type
|
||||
static constexpr bool is_composite = false;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST u32 type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.u32(); }
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic u32 type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
return b.create<sem::U32>();
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value u32 will be initialized with
|
||||
/// @return a new AST u32 literal value expression
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
|
||||
return b.Expr(static_cast<u32>(elem_value));
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building f32 types and expressions
|
||||
template <>
|
||||
struct DataType<f32> {
|
||||
/// false as f32 is not a composite type
|
||||
static constexpr bool is_composite = false;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST f32 type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) { return b.ty.f32(); }
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic f32 type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
return b.create<sem::F32>();
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value f32 will be initialized with
|
||||
/// @return a new AST f32 literal value expression
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
|
||||
return b.Expr(static_cast<f32>(elem_value));
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building vector types and expressions
|
||||
template <int N, typename T>
|
||||
struct DataType<vec<N, T>> {
|
||||
/// true as vectors are a composite type
|
||||
static constexpr bool is_composite = true;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST vector type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) {
|
||||
return b.ty.vec(DataType<T>::AST(b), N);
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic vector type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
return b.create<sem::Vector>(DataType<T>::Sem(b), N);
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value each element in the vector will be initialized
|
||||
/// with
|
||||
/// @return a new AST vector value expression
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
|
||||
return b.Construct(AST(b), ExprArgs(b, elem_value));
|
||||
}
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value each element will be initialized with
|
||||
/// @return the list of expressions that are used to construct the vector
|
||||
static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
|
||||
int elem_value) {
|
||||
ast::ExpressionList args;
|
||||
for (int i = 0; i < N; i++) {
|
||||
args.emplace_back(DataType<T>::Expr(b, elem_value));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building matrix types and expressions
|
||||
template <int N, int M, typename T>
|
||||
struct DataType<mat<N, M, T>> {
|
||||
/// true as matrices are a composite type
|
||||
static constexpr bool is_composite = true;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST matrix type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) {
|
||||
return b.ty.mat(DataType<T>::AST(b), N, M);
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic matrix type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
auto* column_type = b.create<sem::Vector>(DataType<T>::Sem(b), M);
|
||||
return b.create<sem::Matrix>(column_type, N);
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value each element in the matrix will be initialized
|
||||
/// with
|
||||
/// @return a new AST matrix value expression
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
|
||||
return b.Construct(AST(b), ExprArgs(b, elem_value));
|
||||
}
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value each element will be initialized with
|
||||
/// @return the list of expressions that are used to construct the matrix
|
||||
static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
|
||||
int elem_value) {
|
||||
ast::ExpressionList args;
|
||||
for (int i = 0; i < N; i++) {
|
||||
args.emplace_back(DataType<vec<M, T>>::Expr(b, elem_value));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building alias types and expressions
|
||||
template <typename T, int ID>
|
||||
struct DataType<alias<T, ID>> {
|
||||
/// true if the aliased type is a composite type
|
||||
static constexpr bool is_composite = DataType<T>::is_composite;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST alias type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) {
|
||||
auto name = b.Symbols().Register("alias_" + std::to_string(ID));
|
||||
if (!b.AST().LookupType(name)) {
|
||||
auto* type = DataType<T>::AST(b);
|
||||
b.AST().AddTypeDecl(b.ty.alias(name, type));
|
||||
}
|
||||
return b.create<ast::TypeName>(name);
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic aliased type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
return DataType<T>::Sem(b);
|
||||
}
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value nested elements will be initialized with
|
||||
/// @return a new AST expression of the alias type
|
||||
template <bool IS_COMPOSITE = is_composite>
|
||||
static inline traits::EnableIf<!IS_COMPOSITE, const ast::Expression*> Expr(
|
||||
ProgramBuilder& b,
|
||||
int elem_value) {
|
||||
// Cast
|
||||
return b.Construct(AST(b), DataType<T>::Expr(b, elem_value));
|
||||
}
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value nested elements will be initialized with
|
||||
/// @return a new AST expression of the alias type
|
||||
template <bool IS_COMPOSITE = is_composite>
|
||||
static inline traits::EnableIf<IS_COMPOSITE, const ast::Expression*> Expr(
|
||||
ProgramBuilder& b,
|
||||
int elem_value) {
|
||||
// Construct
|
||||
return b.Construct(AST(b), DataType<T>::ExprArgs(b, elem_value));
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building pointer types and expressions
|
||||
template <typename T>
|
||||
struct DataType<ptr<T>> {
|
||||
/// true if the pointer type is a composite type
|
||||
static constexpr bool is_composite = false;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST alias type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) {
|
||||
return b.create<ast::Pointer>(DataType<T>::AST(b),
|
||||
ast::StorageClass::kPrivate,
|
||||
ast::Access::kReadWrite);
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic aliased type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
return b.create<sem::Pointer>(DataType<T>::Sem(b),
|
||||
ast::StorageClass::kPrivate,
|
||||
ast::Access::kReadWrite);
|
||||
}
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST expression of the alias type
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int /*unused*/) {
|
||||
auto sym = b.Symbols().New("global_for_ptr");
|
||||
b.Global(sym, DataType<T>::AST(b), ast::StorageClass::kPrivate);
|
||||
return b.AddressOf(sym);
|
||||
}
|
||||
};
|
||||
|
||||
/// Helper for building array types and expressions
|
||||
template <int N, typename T>
|
||||
struct DataType<array<N, T>> {
|
||||
/// true as arrays are a composite type
|
||||
static constexpr bool is_composite = true;
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return a new AST array type
|
||||
static inline const ast::Type* AST(ProgramBuilder& b) {
|
||||
return b.ty.array(DataType<T>::AST(b), N);
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @return the semantic array type
|
||||
static inline const sem::Type* Sem(ProgramBuilder& b) {
|
||||
auto* el = DataType<T>::Sem(b);
|
||||
return b.create<sem::Array>(
|
||||
/* element */ el,
|
||||
/* count */ N,
|
||||
/* align */ el->Align(),
|
||||
/* size */ el->Size(),
|
||||
/* stride */ el->Align(),
|
||||
/* implicit_stride */ el->Align());
|
||||
}
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value each element in the array will be initialized
|
||||
/// with
|
||||
/// @return a new AST array value expression
|
||||
static inline const ast::Expression* Expr(ProgramBuilder& b, int elem_value) {
|
||||
return b.Construct(AST(b), ExprArgs(b, elem_value));
|
||||
}
|
||||
|
||||
/// @param b the ProgramBuilder
|
||||
/// @param elem_value the value each element will be initialized with
|
||||
/// @return the list of expressions that are used to construct the array
|
||||
static inline ast::ExpressionList ExprArgs(ProgramBuilder& b,
|
||||
int elem_value) {
|
||||
ast::ExpressionList args;
|
||||
for (int i = 0; i < N; i++) {
|
||||
args.emplace_back(DataType<T>::Expr(b, elem_value));
|
||||
}
|
||||
return args;
|
||||
}
|
||||
};
|
||||
|
||||
/// Struct of all creation pointer types
|
||||
struct CreatePtrs {
|
||||
/// ast node type create function
|
||||
ast_type_func_ptr ast;
|
||||
/// ast expression type create function
|
||||
ast_expr_func_ptr expr;
|
||||
/// sem type create function
|
||||
sem_type_func_ptr sem;
|
||||
};
|
||||
|
||||
/// Returns a CreatePtrs struct instance with all creation pointer types for
|
||||
/// type `T`
|
||||
template <typename T>
|
||||
constexpr CreatePtrs CreatePtrsFor() {
|
||||
return {DataType<T>::AST, DataType<T>::Expr, DataType<T>::Sem};
|
||||
}
|
||||
|
||||
} // namespace builder
|
||||
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_TINT_RESOLVER_RESOLVER_TEST_HELPER_H_
|
||||
2369
src/tint/resolver/resolver_validation.cc
Normal file
2369
src/tint/resolver/resolver_validation.cc
Normal file
File diff suppressed because it is too large
Load Diff
371
src/tint/resolver/side_effects_test.cc
Normal file
371
src/tint/resolver/side_effects_test.cc
Normal file
@@ -0,0 +1,371 @@
|
||||
// Copyright 2022 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/expression.h"
|
||||
#include "src/tint/sem/member_accessor_expression.h"
|
||||
|
||||
namespace tint::resolver {
|
||||
namespace {
|
||||
|
||||
struct SideEffectsTest : ResolverTest {
|
||||
template <typename T>
|
||||
void MakeSideEffectFunc(const char* name) {
|
||||
auto global = Sym();
|
||||
Global(global, ty.Of<T>(), ast::StorageClass::kPrivate);
|
||||
auto local = Sym();
|
||||
Func(name, {}, ty.Of<T>(),
|
||||
{
|
||||
Decl(Var(local, ty.Of<T>())),
|
||||
Assign(global, local),
|
||||
Return(global),
|
||||
});
|
||||
}
|
||||
|
||||
template <typename MAKE_TYPE_FUNC>
|
||||
void MakeSideEffectFunc(const char* name, MAKE_TYPE_FUNC make_type) {
|
||||
auto global = Sym();
|
||||
Global(global, make_type(), ast::StorageClass::kPrivate);
|
||||
auto local = Sym();
|
||||
Func(name, {}, make_type(),
|
||||
{
|
||||
Decl(Var(local, make_type())),
|
||||
Assign(global, local),
|
||||
Return(global),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SideEffectsTest, Phony) {
|
||||
auto* expr = Phony();
|
||||
auto* body = Assign(expr, 1);
|
||||
WrapInFunction(body);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Literal) {
|
||||
auto* expr = Expr(1);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, VariableUser) {
|
||||
auto* var = Decl(Var("a", ty.i32()));
|
||||
auto* expr = Expr("a");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::VariableUser>());
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_Builtin_NoSE) {
|
||||
Global("a", ty.f32(), ast::StorageClass::kPrivate);
|
||||
auto* expr = Call("dpdx", "a");
|
||||
Func("f", {}, ty.void_(), {Ignore(expr)},
|
||||
{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_Builtin_NoSE_WithSEArg) {
|
||||
MakeSideEffectFunc<f32>("se");
|
||||
auto* expr = Call("dpdx", Call("se"));
|
||||
Func("f", {}, ty.void_(), {Ignore(expr)},
|
||||
{create<ast::StageAttribute>(ast::PipelineStage::kFragment)});
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_Builtin_SE) {
|
||||
Global("a", ty.atomic(ty.i32()), ast::StorageClass::kWorkgroup);
|
||||
auto* expr = Call("atomicAdd", AddressOf("a"), 1);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_Function) {
|
||||
Func("f", {}, ty.i32(), {Return(1)});
|
||||
auto* expr = Call("f");
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_TypeConversion_NoSE) {
|
||||
auto* var = Decl(Var("a", ty.i32()));
|
||||
auto* expr = Construct(ty.f32(), "a");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_TypeConversion_SE) {
|
||||
MakeSideEffectFunc<i32>("se");
|
||||
auto* expr = Construct(ty.f32(), Call("se"));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_TypeConstructor_NoSE) {
|
||||
auto* var = Decl(Var("a", ty.f32()));
|
||||
auto* expr = Construct(ty.f32(), "a");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Call_TypeConstructor_SE) {
|
||||
MakeSideEffectFunc<f32>("se");
|
||||
auto* expr = Construct(ty.f32(), Call("se"));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->Is<sem::Call>());
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, MemberAccessor_Struct_NoSE) {
|
||||
auto* s = Structure("S", {Member("m", ty.i32())});
|
||||
auto* var = Decl(Var("a", ty.Of(s)));
|
||||
auto* expr = MemberAccessor("a", "m");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, MemberAccessor_Struct_SE) {
|
||||
auto* s = Structure("S", {Member("m", ty.i32())});
|
||||
MakeSideEffectFunc("se", [&] { return ty.Of(s); });
|
||||
auto* expr = MemberAccessor(Call("se"), "m");
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, MemberAccessor_Vector) {
|
||||
auto* var = Decl(Var("a", ty.vec4<f32>()));
|
||||
auto* expr = MemberAccessor("a", "x");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_TRUE(sem->Is<sem::MemberAccessorExpression>());
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, MemberAccessor_VectorSwizzle) {
|
||||
auto* var = Decl(Var("a", ty.vec4<f32>()));
|
||||
auto* expr = MemberAccessor("a", "xzyw");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
EXPECT_TRUE(sem->Is<sem::Swizzle>());
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Binary_NoSE) {
|
||||
auto* a = Decl(Var("a", ty.i32()));
|
||||
auto* b = Decl(Var("b", ty.i32()));
|
||||
auto* expr = Add("a", "b");
|
||||
WrapInFunction(a, b, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Binary_LeftSE) {
|
||||
MakeSideEffectFunc<i32>("se");
|
||||
auto* b = Decl(Var("b", ty.i32()));
|
||||
auto* expr = Add(Call("se"), "b");
|
||||
WrapInFunction(b, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Binary_RightSE) {
|
||||
MakeSideEffectFunc<i32>("se");
|
||||
auto* a = Decl(Var("a", ty.i32()));
|
||||
auto* expr = Add("a", Call("se"));
|
||||
WrapInFunction(a, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Binary_BothSE) {
|
||||
MakeSideEffectFunc<i32>("se1");
|
||||
MakeSideEffectFunc<i32>("se2");
|
||||
auto* expr = Add(Call("se1"), Call("se2"));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Unary_NoSE) {
|
||||
auto* var = Decl(Var("a", ty.bool_()));
|
||||
auto* expr = Not("a");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Unary_SE) {
|
||||
MakeSideEffectFunc<bool>("se");
|
||||
auto* expr = Not(Call("se"));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, IndexAccessor_NoSE) {
|
||||
auto* var = Decl(Var("a", ty.array<i32, 10>()));
|
||||
auto* expr = IndexAccessor("a", 0);
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, IndexAccessor_ObjSE) {
|
||||
MakeSideEffectFunc("se", [&] { return ty.array<i32, 10>(); });
|
||||
auto* expr = IndexAccessor(Call("se"), 0);
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, IndexAccessor_IndexSE) {
|
||||
MakeSideEffectFunc<i32>("se");
|
||||
auto* var = Decl(Var("a", ty.array<i32, 10>()));
|
||||
auto* expr = IndexAccessor("a", Call("se"));
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, IndexAccessor_BothSE) {
|
||||
MakeSideEffectFunc("se1", [&] { return ty.array<i32, 10>(); });
|
||||
MakeSideEffectFunc<i32>("se2");
|
||||
auto* expr = IndexAccessor(Call("se1"), Call("se2"));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Bitcast_NoSE) {
|
||||
auto* var = Decl(Var("a", ty.i32()));
|
||||
auto* expr = Bitcast<f32>("a");
|
||||
WrapInFunction(var, expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_FALSE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
TEST_F(SideEffectsTest, Bitcast_SE) {
|
||||
MakeSideEffectFunc<i32>("se");
|
||||
auto* expr = Bitcast<f32>(Call("se"));
|
||||
WrapInFunction(expr);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
auto* sem = Sem().Get(expr);
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->HasSideEffects());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tint::resolver
|
||||
573
src/tint/resolver/storage_class_layout_validation_test.cc
Normal file
573
src/tint/resolver/storage_class_layout_validation_test.cc
Normal file
@@ -0,0 +1,573 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverStorageClassLayoutValidationTest = ResolverTest;
|
||||
|
||||
// Detect unaligned member for storage buffers
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
StorageBuffer_UnalignedMember) {
|
||||
// [[block]]
|
||||
// struct S {
|
||||
// @size(5) a : f32;
|
||||
// @align(1) b : f32;
|
||||
// };
|
||||
// @group(0) @binding(0)
|
||||
// var<storage> a : S;
|
||||
|
||||
Structure(Source{{12, 34}}, "S",
|
||||
{Member("a", ty.f32(), {MemberSize(5)}),
|
||||
Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(1)})},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
|
||||
GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(34:56 error: the offset of a struct member of type 'f32' in storage class 'storage' must be a multiple of 4 bytes, but 'b' is currently at offset 5. Consider setting @align(4) on this member
|
||||
12:34 note: see layout of struct:
|
||||
/* align(4) size(12) */ struct S {
|
||||
/* offset(0) align(4) size( 5) */ a : f32;
|
||||
/* offset(5) align(1) size( 4) */ b : f32;
|
||||
/* offset(9) align(1) size( 3) */ // -- implicit struct size padding --;
|
||||
/* */ };
|
||||
78:90 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
StorageBuffer_UnalignedMember_SuggestedFix) {
|
||||
// [[block]]
|
||||
// struct S {
|
||||
// @size(5) a : f32;
|
||||
// @align(4) b : f32;
|
||||
// };
|
||||
// @group(0) @binding(0)
|
||||
// var<storage> a : S;
|
||||
|
||||
Structure(Source{{12, 34}}, "S",
|
||||
{Member("a", ty.f32(), {MemberSize(5)}),
|
||||
Member(Source{{34, 56}}, "b", ty.f32(), {MemberAlign(4)})},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("S"), ast::StorageClass::kStorage,
|
||||
GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
// Detect unaligned struct member for uniform buffers
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_UnalignedMember_Struct) {
|
||||
// struct Inner {
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// scalar : f32;
|
||||
// inner : Inner;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
|
||||
|
||||
Structure(Source{{34, 56}}, "Outer",
|
||||
{
|
||||
Member("scalar", ty.f32()),
|
||||
Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: the offset of a struct member of type 'Inner' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting @align(16) on this member
|
||||
34:56 note: see layout of struct:
|
||||
/* align(4) size(8) */ struct Outer {
|
||||
/* offset(0) align(4) size(4) */ scalar : f32;
|
||||
/* offset(4) align(4) size(4) */ inner : Inner;
|
||||
/* */ };
|
||||
12:34 note: and layout of struct member:
|
||||
/* align(4) size(4) */ struct Inner {
|
||||
/* offset(0) align(4) size(4) */ scalar : i32;
|
||||
/* */ };
|
||||
78:90 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_UnalignedMember_Struct_SuggestedFix) {
|
||||
// struct Inner {
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// scalar : f32;
|
||||
// @align(16) inner : Inner;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Structure(Source{{12, 34}}, "Inner", {Member("scalar", ty.i32())});
|
||||
|
||||
Structure(Source{{34, 56}}, "Outer",
|
||||
{
|
||||
Member("scalar", ty.f32()),
|
||||
Member(Source{{56, 78}}, "inner", ty.type_name("Inner"),
|
||||
{MemberAlign(16)}),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
// Detect unaligned array member for uniform buffers
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_UnalignedMember_Array) {
|
||||
// type Inner = @stride(16) array<f32, 10>;
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// scalar : f32;
|
||||
// inner : Inner;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
Alias("Inner", ty.array(ty.f32(), 10, 16));
|
||||
|
||||
Structure(Source{{12, 34}}, "Outer",
|
||||
{
|
||||
Member("scalar", ty.f32()),
|
||||
Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: the offset of a struct member of type '@stride(16) array<f32, 10>' in storage class 'uniform' must be a multiple of 16 bytes, but 'inner' is currently at offset 4. Consider setting @align(16) on this member
|
||||
12:34 note: see layout of struct:
|
||||
/* align(4) size(164) */ struct Outer {
|
||||
/* offset( 0) align(4) size( 4) */ scalar : f32;
|
||||
/* offset( 4) align(4) size(160) */ inner : @stride(16) array<f32, 10>;
|
||||
/* */ };
|
||||
78:90 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_UnalignedMember_Array_SuggestedFix) {
|
||||
// type Inner = @stride(16) array<f32, 10>;
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// scalar : f32;
|
||||
// @align(16) inner : Inner;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
Alias("Inner", ty.array(ty.f32(), 10, 16));
|
||||
|
||||
Structure(Source{{12, 34}}, "Outer",
|
||||
{
|
||||
Member("scalar", ty.f32()),
|
||||
Member(Source{{34, 56}}, "inner", ty.type_name("Inner"),
|
||||
{MemberAlign(16)}),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
// Detect uniform buffers with byte offset between 2 members that is not a
|
||||
// multiple of 16 bytes
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_MembersOffsetNotMultipleOf16) {
|
||||
// struct Inner {
|
||||
// @align(1) @size(5) scalar : i32;
|
||||
// };
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// inner : Inner;
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Structure(Source{{12, 34}}, "Inner",
|
||||
{Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
|
||||
|
||||
Structure(Source{{34, 56}}, "Outer",
|
||||
{
|
||||
Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
|
||||
Member(Source{{78, 90}}, "scalar", ty.i32()),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{22, 24}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(78:90 error: uniform storage requires that the number of bytes between the start of the previous member of type struct and the current member be a multiple of 16 bytes, but there are currently 8 bytes between 'inner' and 'scalar'. Consider setting @align(16) on this member
|
||||
34:56 note: see layout of struct:
|
||||
/* align(4) size(12) */ struct Outer {
|
||||
/* offset( 0) align(1) size( 5) */ inner : Inner;
|
||||
/* offset( 5) align(1) size( 3) */ // -- implicit field alignment padding --;
|
||||
/* offset( 8) align(4) size( 4) */ scalar : i32;
|
||||
/* */ };
|
||||
12:34 note: and layout of previous member struct:
|
||||
/* align(1) size(5) */ struct Inner {
|
||||
/* offset(0) align(1) size(5) */ scalar : i32;
|
||||
/* */ };
|
||||
22:24 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
// See https://crbug.com/tint/1344
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_MembersOffsetNotMultipleOf16_InnerMoreMembersThanOuter) {
|
||||
// struct Inner {
|
||||
// a : i32;
|
||||
// b : i32;
|
||||
// c : i32;
|
||||
// @align(1) @size(5) scalar : i32;
|
||||
// };
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// inner : Inner;
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Structure(Source{{12, 34}}, "Inner",
|
||||
{
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.i32()),
|
||||
Member("c", ty.i32()),
|
||||
Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)}),
|
||||
});
|
||||
|
||||
Structure(Source{{34, 56}}, "Outer",
|
||||
{
|
||||
Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
|
||||
Member(Source{{78, 90}}, "scalar", ty.i32()),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{22, 24}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(78:90 error: uniform storage requires that the number of bytes between the start of the previous member of type struct and the current member be a multiple of 16 bytes, but there are currently 20 bytes between 'inner' and 'scalar'. Consider setting @align(16) on this member
|
||||
34:56 note: see layout of struct:
|
||||
/* align(4) size(24) */ struct Outer {
|
||||
/* offset( 0) align(4) size(20) */ inner : Inner;
|
||||
/* offset(20) align(4) size( 4) */ scalar : i32;
|
||||
/* */ };
|
||||
12:34 note: and layout of previous member struct:
|
||||
/* align(4) size(20) */ struct Inner {
|
||||
/* offset( 0) align(4) size( 4) */ a : i32;
|
||||
/* offset( 4) align(4) size( 4) */ b : i32;
|
||||
/* offset( 8) align(4) size( 4) */ c : i32;
|
||||
/* offset(12) align(1) size( 5) */ scalar : i32;
|
||||
/* offset(17) align(1) size( 3) */ // -- implicit struct size padding --;
|
||||
/* */ };
|
||||
22:24 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_MembersOffsetNotMultipleOf16_SuggestedFix) {
|
||||
// struct Inner {
|
||||
// @align(1) @size(5) scalar : i32;
|
||||
// };
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// @align(16) inner : Inner;
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Structure(Source{{12, 34}}, "Inner",
|
||||
{Member("scalar", ty.i32(), {MemberAlign(1), MemberSize(5)})});
|
||||
|
||||
Structure(Source{{34, 56}}, "Outer",
|
||||
{
|
||||
Member(Source{{56, 78}}, "inner", ty.type_name("Inner")),
|
||||
Member(Source{{78, 90}}, "scalar", ty.i32(), {MemberAlign(16)}),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{22, 34}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
// Make sure that this doesn't fail validation because vec3's align is 16, but
|
||||
// size is 12. 's' should be at offset 12, which is okay here.
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_Vec3MemberOffset_NoFail) {
|
||||
// [[block]]
|
||||
// struct ScalarPackedAtEndOfVec3 {
|
||||
// v : vec3<f32>;
|
||||
// s : f32;
|
||||
// };
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : ScalarPackedAtEndOfVec3;
|
||||
|
||||
Structure("ScalarPackedAtEndOfVec3",
|
||||
{
|
||||
Member("v", ty.vec3(ty.f32())),
|
||||
Member("s", ty.f32()),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("ScalarPackedAtEndOfVec3"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
// Detect array stride must be a multiple of 16 bytes for uniform buffers
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_InvalidArrayStride_Scalar) {
|
||||
// type Inner = array<f32, 10>;
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// inner : Inner;
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Alias("Inner", ty.array(ty.f32(), 10));
|
||||
|
||||
Structure(Source{{12, 34}}, "Outer",
|
||||
{
|
||||
Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
|
||||
Member("scalar", ty.i32()),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.
|
||||
12:34 note: see layout of struct:
|
||||
/* align(4) size(44) */ struct Outer {
|
||||
/* offset( 0) align(4) size(40) */ inner : array<f32, 10>;
|
||||
/* offset(40) align(4) size( 4) */ scalar : i32;
|
||||
/* */ };
|
||||
78:90 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_InvalidArrayStride_Vector) {
|
||||
// type Inner = array<vec2<f32>, 10>;
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// inner : Inner;
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Alias("Inner", ty.array(ty.vec2<f32>(), 10));
|
||||
|
||||
Structure(Source{{12, 34}}, "Outer",
|
||||
{
|
||||
Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
|
||||
Member("scalar", ty.i32()),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 8. Consider using a vec4 instead.
|
||||
12:34 note: see layout of struct:
|
||||
/* align(8) size(88) */ struct Outer {
|
||||
/* offset( 0) align(8) size(80) */ inner : array<vec2<f32>, 10>;
|
||||
/* offset(80) align(4) size( 4) */ scalar : i32;
|
||||
/* offset(84) align(1) size( 4) */ // -- implicit struct size padding --;
|
||||
/* */ };
|
||||
78:90 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_InvalidArrayStride_Struct) {
|
||||
// struct ArrayElem {
|
||||
// a : f32;
|
||||
// b : i32;
|
||||
// }
|
||||
// type Inner = array<ArrayElem, 10>;
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// inner : Inner;
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
auto* array_elem = Structure("ArrayElem", {
|
||||
Member("a", ty.f32()),
|
||||
Member("b", ty.i32()),
|
||||
});
|
||||
Alias("Inner", ty.array(ty.Of(array_elem), 10));
|
||||
|
||||
Structure(Source{{12, 34}}, "Outer",
|
||||
{
|
||||
Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
|
||||
Member("scalar", ty.i32()),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 8. Consider using the @size attribute on the last struct member.
|
||||
12:34 note: see layout of struct:
|
||||
/* align(4) size(84) */ struct Outer {
|
||||
/* offset( 0) align(4) size(80) */ inner : array<ArrayElem, 10>;
|
||||
/* offset(80) align(4) size( 4) */ scalar : i32;
|
||||
/* */ };
|
||||
78:90 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_InvalidArrayStride_TopLevelArray) {
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : array<f32, 4>;
|
||||
Global(Source{{78, 90}}, "a", ty.array(Source{{34, 56}}, ty.f32(), 4),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_InvalidArrayStride_NestedArray) {
|
||||
// struct Outer {
|
||||
// inner : array<array<f32, 4>, 4>
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : array<Outer, 4>;
|
||||
|
||||
Structure(
|
||||
Source{{12, 34}}, "Outer",
|
||||
{
|
||||
Member("inner", ty.array(Source{{34, 56}}, ty.array(ty.f32(), 4), 4)),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.
|
||||
12:34 note: see layout of struct:
|
||||
/* align(4) size(64) */ struct Outer {
|
||||
/* offset( 0) align(4) size(64) */ inner : array<array<f32, 4>, 4>;
|
||||
/* */ };
|
||||
78:90 note: see declaration of variable)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassLayoutValidationTest,
|
||||
UniformBuffer_InvalidArrayStride_SuggestedFix) {
|
||||
// type Inner = @stride(16) array<f32, 10>;
|
||||
//
|
||||
// [[block]]
|
||||
// struct Outer {
|
||||
// inner : Inner;
|
||||
// scalar : i32;
|
||||
// };
|
||||
//
|
||||
// @group(0) @binding(0)
|
||||
// var<uniform> a : Outer;
|
||||
|
||||
Alias("Inner", ty.array(ty.f32(), 10, 16));
|
||||
|
||||
Structure(Source{{12, 34}}, "Outer",
|
||||
{
|
||||
Member("inner", ty.type_name(Source{{34, 56}}, "Inner")),
|
||||
Member("scalar", ty.i32()),
|
||||
},
|
||||
{StructBlock()});
|
||||
|
||||
Global(Source{{78, 90}}, "a", ty.type_name("Outer"),
|
||||
ast::StorageClass::kUniform, GroupAndBinding(0, 0));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
370
src/tint/resolver/storage_class_validation_test.cc
Normal file
370
src/tint/resolver/storage_class_validation_test.cc
Normal file
@@ -0,0 +1,370 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/struct.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverStorageClassValidationTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, GlobalVariableNoStorageClass_Fail) {
|
||||
// var g : f32;
|
||||
Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kNone);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: global variables must have a storage class");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest,
|
||||
GlobalVariableFunctionStorageClass_Fail) {
|
||||
// var<function> g : f32;
|
||||
Global(Source{{12, 34}}, "g", ty.f32(), ast::StorageClass::kFunction);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: variables declared at module scope must not be in "
|
||||
"the function storage class");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArray) {
|
||||
Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
|
||||
ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
|
||||
12:34 note: while instantiating variable v)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, Private_RuntimeArrayInStruct) {
|
||||
auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
|
||||
Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kPrivate);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
|
||||
note: while analysing structure member S.m
|
||||
12:34 note: while instantiating variable v)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArray) {
|
||||
Global(Source{{12, 34}}, "v", ty.array(ty.i32()),
|
||||
ast::StorageClass::kWorkgroup);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
|
||||
12:34 note: while instantiating variable v)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, Workgroup_RuntimeArrayInStruct) {
|
||||
auto* s = Structure("S", {Member("m", ty.array(ty.i32()))}, {StructBlock()});
|
||||
Global(Source{{12, 34}}, "v", ty.Of(s), ast::StorageClass::kWorkgroup);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
|
||||
note: while analysing structure member S.m
|
||||
12:34 note: while instantiating variable v)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferBool) {
|
||||
// var<storage> g : bool;
|
||||
Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferPointer) {
|
||||
// var<storage> g : ptr<private, f32>;
|
||||
Global(Source{{56, 78}}, "g",
|
||||
ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
|
||||
ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in storage class 'storage' as it is non-host-shareable
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferIntScalar) {
|
||||
// var<storage> g : i32;
|
||||
Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferVector) {
|
||||
// var<storage> g : vec4<f32>;
|
||||
Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferArray) {
|
||||
// var<storage, read> g : array<S, 3>;
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
auto* a = ty.array(ty.Of(s), 3);
|
||||
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferBoolAlias) {
|
||||
// type a = bool;
|
||||
// var<storage, read> g : a;
|
||||
auto* a = Alias("a", ty.bool_());
|
||||
Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'bool' cannot be used in storage class 'storage' as it is non-host-shareable
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, NotStorage_AccessMode) {
|
||||
// var<private, read> g : a;
|
||||
Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kPrivate,
|
||||
ast::Access::kRead);
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: only variables in <storage> storage class may declare an access mode)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Basic) {
|
||||
// [[block]] struct S { x : i32 };
|
||||
// var<storage, read> g : S;
|
||||
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, StorageBufferNoError_Aliases) {
|
||||
// [[block]] struct S { x : i32 };
|
||||
// type a1 = S;
|
||||
// var<storage, read> g : a1;
|
||||
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* a1 = Alias("a1", ty.Of(s));
|
||||
auto* a2 = Alias("a2", ty.Of(a1));
|
||||
Global(Source{{56, 78}}, "g", ty.Of(a2), ast::StorageClass::kStorage,
|
||||
ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve());
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBuffer_Struct_Runtime) {
|
||||
// [[block]] struct S { m: array<f32>; };
|
||||
// @group(0) @binding(0) var<uniform, > svar : S;
|
||||
|
||||
auto* s = Structure(Source{{12, 34}}, "S", {Member("m", ty.array<i32>())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
|
||||
Global(Source{{56, 78}}, "svar", ty.Of(s), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: runtime-sized arrays can only be used in the <storage> storage class
|
||||
note: while analysing structure member S.m
|
||||
56:78 note: while instantiating variable svar)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferBool) {
|
||||
// var<uniform> g : bool;
|
||||
Global(Source{{56, 78}}, "g", ty.bool_(), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'bool' cannot be used in storage class 'uniform' as it is non-host-shareable
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferPointer) {
|
||||
// var<uniform> g : ptr<private, f32>;
|
||||
Global(Source{{56, 78}}, "g",
|
||||
ty.pointer(ty.f32(), ast::StorageClass::kPrivate),
|
||||
ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in storage class 'uniform' as it is non-host-shareable
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferIntScalar) {
|
||||
// var<uniform> g : i32;
|
||||
Global(Source{{56, 78}}, "g", ty.i32(), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferVector) {
|
||||
// var<uniform> g : vec4<f32>;
|
||||
Global(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferArray) {
|
||||
// struct S {
|
||||
// @size(16) f : f32;
|
||||
// }
|
||||
// var<uniform> g : array<S, 3>;
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {MemberSize(16)})});
|
||||
auto* a = ty.array(ty.Of(s), 3);
|
||||
Global(Source{{56, 78}}, "g", a, ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferBoolAlias) {
|
||||
// type a = bool;
|
||||
// var<uniform> g : a;
|
||||
auto* a = Alias("a", ty.bool_());
|
||||
Global(Source{{56, 78}}, "g", ty.Of(a), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(56:78 error: Type 'bool' cannot be used in storage class 'uniform' as it is non-host-shareable
|
||||
56:78 note: while instantiating variable g)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Basic) {
|
||||
// [[block]] struct S { x : i32 };
|
||||
// var<uniform> g : S;
|
||||
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
Global(Source{{56, 78}}, "g", ty.Of(s), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassValidationTest, UniformBufferNoError_Aliases) {
|
||||
// [[block]] struct S { x : i32 };
|
||||
// type a1 = S;
|
||||
// var<uniform> g : a1;
|
||||
auto* s = Structure("S", {Member(Source{{12, 34}}, "x", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* a1 = Alias("a1", ty.Of(s));
|
||||
Global(Source{{56, 78}}, "g", ty.Of(a1), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
410
src/tint/resolver/struct_layout_test.cc
Normal file
410
src/tint/resolver/struct_layout_test.cc
Normal file
@@ -0,0 +1,410 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/struct.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverStructLayoutTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Scalars) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.f32()),
|
||||
Member("b", ty.u32()),
|
||||
Member("c", ty.i32()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 12u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 12u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 8u);
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Alias) {
|
||||
auto* alias_a = Alias("a", ty.f32());
|
||||
auto* alias_b = Alias("b", ty.f32());
|
||||
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.Of(alias_a)),
|
||||
Member("b", ty.Of(alias_b)),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 8u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 8u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 2u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayStaticSize) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.array<i32, 3>()),
|
||||
Member("b", ty.array<f32, 5>()),
|
||||
Member("c", ty.array<f32, 1>()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 36u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 36u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 12u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 12u);
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 20u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 32u);
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayStaticSize) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.array<i32, 3>(/*stride*/ 8)),
|
||||
Member("b", ty.array<f32, 5>(/*stride*/ 16)),
|
||||
Member("c", ty.array<f32, 1>(/*stride*/ 32)),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 136u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 136u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 24u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 24u);
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 80u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 104u);
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayRuntimeSized) {
|
||||
auto* s = Structure("S",
|
||||
{
|
||||
Member("c", ty.array<f32>()),
|
||||
},
|
||||
ast::AttributeList{create<ast::StructBlockAttribute>()});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 4u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 4u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ExplicitStrideArrayRuntimeSized) {
|
||||
auto* s = Structure("S",
|
||||
{
|
||||
Member("c", ty.array<f32>(/*stride*/ 32)),
|
||||
},
|
||||
ast::AttributeList{create<ast::StructBlockAttribute>()});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 32u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 32u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 32u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfExplicitStrideArray) {
|
||||
auto* inner = ty.array<i32, 2>(/*stride*/ 16); // size: 32
|
||||
auto* outer = ty.array(inner, 12); // size: 12 * 32
|
||||
auto* s = Structure("S", {
|
||||
Member("c", outer),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 384u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 384u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 384u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, ImplicitStrideArrayOfStructure) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.vec2<i32>()),
|
||||
Member("b", ty.vec3<i32>()),
|
||||
Member("c", ty.vec4<i32>()),
|
||||
}); // size: 48
|
||||
auto* outer = ty.array(ty.Of(inner), 12); // size: 12 * 48
|
||||
auto* s = Structure("S", {
|
||||
Member("c", outer),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 576u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 576u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 576u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Vector) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.vec2<i32>()),
|
||||
Member("b", ty.vec3<i32>()),
|
||||
Member("c", ty.vec4<i32>()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 48u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 48u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u); // vec2
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 8u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 8u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 16u); // vec3
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 12u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 32u); // vec4
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 16u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, Matrix) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.mat2x2<f32>()),
|
||||
Member("b", ty.mat2x3<f32>()),
|
||||
Member("c", ty.mat2x4<f32>()),
|
||||
Member("d", ty.mat3x2<f32>()),
|
||||
Member("e", ty.mat3x3<f32>()),
|
||||
Member("f", ty.mat3x4<f32>()),
|
||||
Member("g", ty.mat4x2<f32>()),
|
||||
Member("h", ty.mat4x3<f32>()),
|
||||
Member("i", ty.mat4x4<f32>()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 368u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 368u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 9u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u); // mat2x2
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 8u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 16u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 16u); // mat2x3
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 48u); // mat2x4
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[3]->Offset(), 80u); // mat3x2
|
||||
EXPECT_EQ(sem->Members()[3]->Align(), 8u);
|
||||
EXPECT_EQ(sem->Members()[3]->Size(), 24u);
|
||||
EXPECT_EQ(sem->Members()[4]->Offset(), 112u); // mat3x3
|
||||
EXPECT_EQ(sem->Members()[4]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[4]->Size(), 48u);
|
||||
EXPECT_EQ(sem->Members()[5]->Offset(), 160u); // mat3x4
|
||||
EXPECT_EQ(sem->Members()[5]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[5]->Size(), 48u);
|
||||
EXPECT_EQ(sem->Members()[6]->Offset(), 208u); // mat4x2
|
||||
EXPECT_EQ(sem->Members()[6]->Align(), 8u);
|
||||
EXPECT_EQ(sem->Members()[6]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[7]->Offset(), 240u); // mat4x3
|
||||
EXPECT_EQ(sem->Members()[7]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[7]->Size(), 64u);
|
||||
EXPECT_EQ(sem->Members()[8]->Offset(), 304u); // mat4x4
|
||||
EXPECT_EQ(sem->Members()[8]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[8]->Size(), 64u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, NestedStruct) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.mat3x3<f32>()),
|
||||
});
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.i32()),
|
||||
Member("b", ty.Of(inner)),
|
||||
Member("c", ty.i32()),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 80u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 68u);
|
||||
EXPECT_EQ(sem->Align(), 16u);
|
||||
ASSERT_EQ(sem->Members().size(), 3u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 16u);
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 48u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 64u);
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, SizeAttributes) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.f32(), {MemberSize(8)}),
|
||||
Member("b", ty.f32(), {MemberSize(16)}),
|
||||
Member("c", ty.f32(), {MemberSize(8)}),
|
||||
});
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.f32(), {MemberSize(4)}),
|
||||
Member("b", ty.u32(), {MemberSize(8)}),
|
||||
Member("c", ty.Of(inner)),
|
||||
Member("d", ty.i32(), {MemberSize(32)}),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 76u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 76u);
|
||||
EXPECT_EQ(sem->Align(), 4u);
|
||||
ASSERT_EQ(sem->Members().size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 8u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 12u);
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[3]->Offset(), 44u);
|
||||
EXPECT_EQ(sem->Members()[3]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[3]->Size(), 32u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, AlignAttributes) {
|
||||
auto* inner = Structure("Inner", {
|
||||
Member("a", ty.f32(), {MemberAlign(8)}),
|
||||
Member("b", ty.f32(), {MemberAlign(16)}),
|
||||
Member("c", ty.f32(), {MemberAlign(4)}),
|
||||
});
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.f32(), {MemberAlign(4)}),
|
||||
Member("b", ty.u32(), {MemberAlign(8)}),
|
||||
Member("c", ty.Of(inner)),
|
||||
Member("d", ty.i32(), {MemberAlign(32)}),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 96u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 68u);
|
||||
EXPECT_EQ(sem->Align(), 32u);
|
||||
ASSERT_EQ(sem->Members().size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 4u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[1]->Offset(), 8u);
|
||||
EXPECT_EQ(sem->Members()[1]->Align(), 8u);
|
||||
EXPECT_EQ(sem->Members()[1]->Size(), 4u);
|
||||
EXPECT_EQ(sem->Members()[2]->Offset(), 16u);
|
||||
EXPECT_EQ(sem->Members()[2]->Align(), 16u);
|
||||
EXPECT_EQ(sem->Members()[2]->Size(), 32u);
|
||||
EXPECT_EQ(sem->Members()[3]->Offset(), 64u);
|
||||
EXPECT_EQ(sem->Members()[3]->Align(), 32u);
|
||||
EXPECT_EQ(sem->Members()[3]->Size(), 4u);
|
||||
}
|
||||
|
||||
TEST_F(ResolverStructLayoutTest, StructWithLotsOfPadding) {
|
||||
auto* s = Structure("S", {
|
||||
Member("a", ty.i32(), {MemberAlign(1024)}),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_EQ(sem->Size(), 1024u);
|
||||
EXPECT_EQ(sem->SizeNoPadding(), 4u);
|
||||
EXPECT_EQ(sem->Align(), 1024u);
|
||||
ASSERT_EQ(sem->Members().size(), 1u);
|
||||
EXPECT_EQ(sem->Members()[0]->Offset(), 0u);
|
||||
EXPECT_EQ(sem->Members()[0]->Align(), 1024u);
|
||||
EXPECT_EQ(sem->Members()[0]->Size(), 4u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
191
src/tint/resolver/struct_pipeline_stage_use_test.cc
Normal file
191
src/tint/resolver/struct_pipeline_stage_use_test.cc
Normal file
@@ -0,0 +1,191 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/stage_attribute.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/struct.h"
|
||||
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverPipelineStageUseTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, UnusedStruct) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->PipelineStageUses().empty());
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointParam) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
|
||||
Func("foo", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->PipelineStageUses().empty());
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsNonEntryPointReturnType) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
|
||||
Func("foo", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))}, {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->PipelineStageUses().empty());
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderParam) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
|
||||
Func("main", {Param("param", ty.Of(s))}, ty.vec4<f32>(),
|
||||
{Return(Construct(ty.vec4<f32>()))},
|
||||
{Stage(ast::PipelineStage::kVertex)},
|
||||
{Builtin(ast::Builtin::kPosition)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kVertexInput));
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsVertexShaderReturnType) {
|
||||
auto* s = Structure(
|
||||
"S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
|
||||
|
||||
Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
|
||||
{Stage(ast::PipelineStage::kVertex)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput));
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderParam) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
|
||||
Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsFragmentShaderReturnType) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
|
||||
Func("main", {}, ty.Of(s), {Return(Construct(ty.Of(s), Expr(0.f)))},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsComputeShaderParam) {
|
||||
auto* s = Structure(
|
||||
"S",
|
||||
{Member("a", ty.u32(), {Builtin(ast::Builtin::kLocalInvocationIndex)})});
|
||||
|
||||
Func("main", {Param("param", ty.Of(s))}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kCompute), WorkgroupSize(1)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kComputeInput));
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedMultipleStages) {
|
||||
auto* s = Structure(
|
||||
"S", {Member("a", ty.vec4<f32>(), {Builtin(ast::Builtin::kPosition)})});
|
||||
|
||||
Func("vert_main", {}, ty.Of(s), {Return(Construct(ty.Of(s)))},
|
||||
{Stage(ast::PipelineStage::kVertex)});
|
||||
|
||||
Func("frag_main", {Param("param", ty.Of(s))}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kVertexOutput,
|
||||
sem::PipelineStageUsage::kFragmentInput));
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderParamViaAlias) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
auto* s_alias = Alias("S_alias", ty.Of(s));
|
||||
|
||||
Func("main", {Param("param", ty.Of(s_alias))}, ty.void_(), {},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kFragmentInput));
|
||||
}
|
||||
|
||||
TEST_F(ResolverPipelineStageUseTest, StructUsedAsShaderReturnTypeViaAlias) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32(), {Location(0)})});
|
||||
auto* s_alias = Alias("S_alias", ty.Of(s));
|
||||
|
||||
Func("main", {}, ty.Of(s_alias),
|
||||
{Return(Construct(ty.Of(s_alias), Expr(0.f)))},
|
||||
{Stage(ast::PipelineStage::kFragment)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->PipelineStageUses(),
|
||||
UnorderedElementsAre(sem::PipelineStageUsage::kFragmentOutput));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
197
src/tint/resolver/struct_storage_class_use_test.cc
Normal file
197
src/tint/resolver/struct_storage_class_use_test.cc
Normal file
@@ -0,0 +1,197 @@
|
||||
// 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/tint/resolver/resolver.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "src/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/struct.h"
|
||||
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
using ResolverStorageClassUseTest = ResolverTest;
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, UnreachableStruct) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_TRUE(sem->StorageClassUsage().empty());
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableFromParameter) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
|
||||
Func("f", {Param("param", ty.Of(s))}, ty.void_(), {}, {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kNone));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableFromReturnType) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
|
||||
Func("f", {}, ty.Of(s), {Return(Construct(ty.Of(s)))}, {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kNone));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableFromGlobal) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
|
||||
Global("g", ty.Of(s), ast::StorageClass::kPrivate);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kPrivate));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalAlias) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
auto* a = Alias("A", ty.Of(s));
|
||||
Global("g", ty.Of(a), ast::StorageClass::kPrivate);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kPrivate));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalStruct) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
auto* o = Structure("O", {Member("a", ty.Of(s))});
|
||||
Global("g", ty.Of(o), ast::StorageClass::kPrivate);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kPrivate));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableViaGlobalArray) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
auto* a = ty.array(ty.Of(s), 3);
|
||||
Global("g", a, ast::StorageClass::kPrivate);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kPrivate));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableFromLocal) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
|
||||
WrapInFunction(Var("g", ty.Of(s)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kFunction));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalAlias) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
auto* a = Alias("A", ty.Of(s));
|
||||
WrapInFunction(Var("g", ty.Of(a)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kFunction));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalStruct) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
auto* o = Structure("O", {Member("a", ty.Of(s))});
|
||||
WrapInFunction(Var("g", ty.Of(o)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kFunction));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructReachableViaLocalArray) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())});
|
||||
auto* a = ty.array(ty.Of(s), 3);
|
||||
WrapInFunction(Var("g", a));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kFunction));
|
||||
}
|
||||
|
||||
TEST_F(ResolverStorageClassUseTest, StructMultipleStorageClassUses) {
|
||||
auto* s = Structure("S", {Member("a", ty.f32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
Global("x", ty.Of(s), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
Global("y", ty.Of(s), ast::StorageClass::kStorage, ast::Access::kRead,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(1),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
WrapInFunction(Var("g", ty.Of(s)));
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* sem = TypeOf(s)->As<sem::Struct>();
|
||||
ASSERT_NE(sem, nullptr);
|
||||
EXPECT_THAT(sem->StorageClassUsage(),
|
||||
UnorderedElementsAre(ast::StorageClass::kUniform,
|
||||
ast::StorageClass::kStorage,
|
||||
ast::StorageClass::kFunction));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
2937
src/tint/resolver/type_constructor_validation_test.cc
Normal file
2937
src/tint/resolver/type_constructor_validation_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
1163
src/tint/resolver/type_validation_test.cc
Normal file
1163
src/tint/resolver/type_validation_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
1320
src/tint/resolver/validation_test.cc
Normal file
1320
src/tint/resolver/validation_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
701
src/tint/resolver/var_let_test.cc
Normal file
701
src/tint/resolver/var_let_test.cc
Normal file
@@ -0,0 +1,701 @@
|
||||
// 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/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
#include "src/tint/sem/reference_type.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
struct ResolverVarLetTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverVarLetTest, VarDeclWithoutConstructor) {
|
||||
// struct S { i : i32; }
|
||||
// alias A = S;
|
||||
// fn F(){
|
||||
// var i : i32;
|
||||
// var u : u32;
|
||||
// var f : f32;
|
||||
// var b : bool;
|
||||
// var s : S;
|
||||
// var a : A;
|
||||
// }
|
||||
|
||||
auto* S = Structure("S", {Member("i", ty.i32())});
|
||||
auto* A = Alias("A", ty.Of(S));
|
||||
|
||||
auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
|
||||
auto* u = Var("u", ty.u32(), ast::StorageClass::kNone);
|
||||
auto* f = Var("f", ty.f32(), ast::StorageClass::kNone);
|
||||
auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone);
|
||||
auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone);
|
||||
auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone);
|
||||
|
||||
Func("F", {}, ty.void_(),
|
||||
{
|
||||
Decl(i),
|
||||
Decl(u),
|
||||
Decl(f),
|
||||
Decl(b),
|
||||
Decl(s),
|
||||
Decl(a),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
// `var` declarations are always of reference type
|
||||
ASSERT_TRUE(TypeOf(i)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(u)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(f)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(b)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(s)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(a)->Is<sem::Reference>());
|
||||
|
||||
EXPECT_TRUE(TypeOf(i)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
|
||||
EXPECT_TRUE(TypeOf(u)->As<sem::Reference>()->StoreType()->Is<sem::U32>());
|
||||
EXPECT_TRUE(TypeOf(f)->As<sem::Reference>()->StoreType()->Is<sem::F32>());
|
||||
EXPECT_TRUE(TypeOf(b)->As<sem::Reference>()->StoreType()->Is<sem::Bool>());
|
||||
EXPECT_TRUE(TypeOf(s)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
|
||||
EXPECT_TRUE(TypeOf(a)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
|
||||
|
||||
EXPECT_EQ(Sem().Get(i)->Constructor(), nullptr);
|
||||
EXPECT_EQ(Sem().Get(u)->Constructor(), nullptr);
|
||||
EXPECT_EQ(Sem().Get(f)->Constructor(), nullptr);
|
||||
EXPECT_EQ(Sem().Get(b)->Constructor(), nullptr);
|
||||
EXPECT_EQ(Sem().Get(s)->Constructor(), nullptr);
|
||||
EXPECT_EQ(Sem().Get(a)->Constructor(), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, VarDeclWithConstructor) {
|
||||
// struct S { i : i32; }
|
||||
// alias A = S;
|
||||
// fn F(){
|
||||
// var i : i32 = 1;
|
||||
// var u : u32 = 1u;
|
||||
// var f : f32 = 1.f;
|
||||
// var b : bool = true;
|
||||
// var s : S = S(1);
|
||||
// var a : A = A(1);
|
||||
// }
|
||||
|
||||
auto* S = Structure("S", {Member("i", ty.i32())});
|
||||
auto* A = Alias("A", ty.Of(S));
|
||||
|
||||
auto* i_c = Expr(1);
|
||||
auto* u_c = Expr(1u);
|
||||
auto* f_c = Expr(1.f);
|
||||
auto* b_c = Expr(true);
|
||||
auto* s_c = Construct(ty.Of(S), Expr(1));
|
||||
auto* a_c = Construct(ty.Of(A), Expr(1));
|
||||
|
||||
auto* i = Var("i", ty.i32(), ast::StorageClass::kNone, i_c);
|
||||
auto* u = Var("u", ty.u32(), ast::StorageClass::kNone, u_c);
|
||||
auto* f = Var("f", ty.f32(), ast::StorageClass::kNone, f_c);
|
||||
auto* b = Var("b", ty.bool_(), ast::StorageClass::kNone, b_c);
|
||||
auto* s = Var("s", ty.Of(S), ast::StorageClass::kNone, s_c);
|
||||
auto* a = Var("a", ty.Of(A), ast::StorageClass::kNone, a_c);
|
||||
|
||||
Func("F", {}, ty.void_(),
|
||||
{
|
||||
Decl(i),
|
||||
Decl(u),
|
||||
Decl(f),
|
||||
Decl(b),
|
||||
Decl(s),
|
||||
Decl(a),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
// `var` declarations are always of reference type
|
||||
ASSERT_TRUE(TypeOf(i)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(u)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(f)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(b)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(s)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(a)->Is<sem::Reference>());
|
||||
|
||||
EXPECT_TRUE(TypeOf(i)->As<sem::Reference>()->StoreType()->Is<sem::I32>());
|
||||
EXPECT_TRUE(TypeOf(u)->As<sem::Reference>()->StoreType()->Is<sem::U32>());
|
||||
EXPECT_TRUE(TypeOf(f)->As<sem::Reference>()->StoreType()->Is<sem::F32>());
|
||||
EXPECT_TRUE(TypeOf(b)->As<sem::Reference>()->StoreType()->Is<sem::Bool>());
|
||||
EXPECT_TRUE(TypeOf(s)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
|
||||
EXPECT_TRUE(TypeOf(a)->As<sem::Reference>()->StoreType()->Is<sem::Struct>());
|
||||
|
||||
EXPECT_EQ(Sem().Get(i)->Constructor()->Declaration(), i_c);
|
||||
EXPECT_EQ(Sem().Get(u)->Constructor()->Declaration(), u_c);
|
||||
EXPECT_EQ(Sem().Get(f)->Constructor()->Declaration(), f_c);
|
||||
EXPECT_EQ(Sem().Get(b)->Constructor()->Declaration(), b_c);
|
||||
EXPECT_EQ(Sem().Get(s)->Constructor()->Declaration(), s_c);
|
||||
EXPECT_EQ(Sem().Get(a)->Constructor()->Declaration(), a_c);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LetDecl) {
|
||||
// struct S { i : i32; }
|
||||
// fn F(){
|
||||
// var v : i32;
|
||||
// let i : i32 = 1;
|
||||
// let u : u32 = 1u;
|
||||
// let f : f32 = 1.;
|
||||
// let b : bool = true;
|
||||
// let s : S = S(1);
|
||||
// let a : A = A(1);
|
||||
// let p : pointer<function, i32> = &v;
|
||||
// }
|
||||
|
||||
auto* S = Structure("S", {Member("i", ty.i32())});
|
||||
auto* A = Alias("A", ty.Of(S));
|
||||
auto* v = Var("v", ty.i32(), ast::StorageClass::kNone);
|
||||
|
||||
auto* i_c = Expr(1);
|
||||
auto* u_c = Expr(1u);
|
||||
auto* f_c = Expr(1.f);
|
||||
auto* b_c = Expr(true);
|
||||
auto* s_c = Construct(ty.Of(S), Expr(1));
|
||||
auto* a_c = Construct(ty.Of(A), Expr(1));
|
||||
auto* p_c = AddressOf(v);
|
||||
|
||||
auto* i = Const("i", ty.i32(), i_c);
|
||||
auto* u = Const("u", ty.u32(), u_c);
|
||||
auto* f = Const("f", ty.f32(), f_c);
|
||||
auto* b = Const("b", ty.bool_(), b_c);
|
||||
auto* s = Const("s", ty.Of(S), s_c);
|
||||
auto* a = Const("a", ty.Of(A), a_c);
|
||||
auto* p = Const("p", ty.pointer<i32>(ast::StorageClass::kFunction), p_c);
|
||||
|
||||
Func("F", {}, ty.void_(),
|
||||
{
|
||||
Decl(v),
|
||||
Decl(i),
|
||||
Decl(u),
|
||||
Decl(f),
|
||||
Decl(b),
|
||||
Decl(s),
|
||||
Decl(a),
|
||||
Decl(p),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
// `let` declarations are always of the storage type
|
||||
ASSERT_TRUE(TypeOf(i)->Is<sem::I32>());
|
||||
ASSERT_TRUE(TypeOf(u)->Is<sem::U32>());
|
||||
ASSERT_TRUE(TypeOf(f)->Is<sem::F32>());
|
||||
ASSERT_TRUE(TypeOf(b)->Is<sem::Bool>());
|
||||
ASSERT_TRUE(TypeOf(s)->Is<sem::Struct>());
|
||||
ASSERT_TRUE(TypeOf(a)->Is<sem::Struct>());
|
||||
ASSERT_TRUE(TypeOf(p)->Is<sem::Pointer>());
|
||||
ASSERT_TRUE(TypeOf(p)->As<sem::Pointer>()->StoreType()->Is<sem::I32>());
|
||||
|
||||
EXPECT_EQ(Sem().Get(i)->Constructor()->Declaration(), i_c);
|
||||
EXPECT_EQ(Sem().Get(u)->Constructor()->Declaration(), u_c);
|
||||
EXPECT_EQ(Sem().Get(f)->Constructor()->Declaration(), f_c);
|
||||
EXPECT_EQ(Sem().Get(b)->Constructor()->Declaration(), b_c);
|
||||
EXPECT_EQ(Sem().Get(s)->Constructor()->Declaration(), s_c);
|
||||
EXPECT_EQ(Sem().Get(a)->Constructor()->Declaration(), a_c);
|
||||
EXPECT_EQ(Sem().Get(p)->Constructor()->Declaration(), p_c);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, DefaultVarStorageClass) {
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
|
||||
|
||||
auto* buf = Structure("S", {Member("m", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* function = Var("f", ty.i32());
|
||||
auto* private_ = Global("p", ty.i32(), ast::StorageClass::kPrivate);
|
||||
auto* workgroup = Global("w", ty.i32(), ast::StorageClass::kWorkgroup);
|
||||
auto* uniform = Global("ub", ty.Of(buf), ast::StorageClass::kUniform,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(1),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
auto* handle = Global("h", ty.depth_texture(ast::TextureDimension::k2d),
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(2),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
WrapInFunction(function);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(function)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(private_)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(workgroup)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(uniform)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(handle)->Is<sem::Reference>());
|
||||
|
||||
EXPECT_EQ(TypeOf(function)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(private_)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(workgroup)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(uniform)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kRead);
|
||||
EXPECT_EQ(TypeOf(handle)->As<sem::Reference>()->Access(), ast::Access::kRead);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, ExplicitVarStorageClass) {
|
||||
// https://gpuweb.github.io/gpuweb/wgsl/#storage-class
|
||||
|
||||
auto* buf = Structure("S", {Member("m", ty.i32())},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* storage = Global("sb", ty.Of(buf), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(1),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(storage)->Is<sem::Reference>());
|
||||
|
||||
EXPECT_EQ(TypeOf(storage)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LetInheritsAccessFromOriginatingVariable) {
|
||||
// struct Inner {
|
||||
// arr: array<i32, 4>;
|
||||
// }
|
||||
// [[block]] struct S {
|
||||
// inner: Inner;
|
||||
// }
|
||||
// @group(0) @binding(0) var<storage, read_write> s : S;
|
||||
// fn f() {
|
||||
// let p = &s.inner.arr[2];
|
||||
// }
|
||||
auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
|
||||
auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
|
||||
ast::Access::kReadWrite,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
auto* expr =
|
||||
IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
|
||||
auto* ptr = Const("p", nullptr, AddressOf(expr));
|
||||
|
||||
WrapInFunction(ptr);
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
ASSERT_TRUE(TypeOf(expr)->Is<sem::Reference>());
|
||||
ASSERT_TRUE(TypeOf(ptr)->Is<sem::Pointer>());
|
||||
|
||||
EXPECT_EQ(TypeOf(expr)->As<sem::Reference>()->Access(),
|
||||
ast::Access::kReadWrite);
|
||||
EXPECT_EQ(TypeOf(ptr)->As<sem::Pointer>()->Access(), ast::Access::kReadWrite);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsAlias) {
|
||||
// type a = i32;
|
||||
//
|
||||
// fn X() {
|
||||
// var a = false;
|
||||
// }
|
||||
//
|
||||
// fn Y() {
|
||||
// let a = true;
|
||||
// }
|
||||
|
||||
auto* t = Alias("a", ty.i32());
|
||||
auto* v = Var("a", nullptr, Expr(false));
|
||||
auto* l = Const("a", nullptr, Expr(false));
|
||||
Func("X", {}, ty.void_(), {Decl(v)});
|
||||
Func("Y", {}, ty.void_(), {Decl(l)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* type_t = Sem().Get(t);
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), type_t);
|
||||
EXPECT_EQ(local_l->Shadows(), type_t);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsStruct) {
|
||||
// struct a {
|
||||
// m : i32;
|
||||
// };
|
||||
//
|
||||
// fn X() {
|
||||
// var a = true;
|
||||
// }
|
||||
//
|
||||
// fn Y() {
|
||||
// let a = false;
|
||||
// }
|
||||
|
||||
auto* t = Structure("a", {Member("m", ty.i32())});
|
||||
auto* v = Var("a", nullptr, Expr(false));
|
||||
auto* l = Const("a", nullptr, Expr(false));
|
||||
Func("X", {}, ty.void_(), {Decl(v)});
|
||||
Func("Y", {}, ty.void_(), {Decl(l)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* type_t = Sem().Get(t);
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), type_t);
|
||||
EXPECT_EQ(local_l->Shadows(), type_t);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsFunction) {
|
||||
// fn a() {
|
||||
// var a = true;
|
||||
// }
|
||||
//
|
||||
// fn b() {
|
||||
// let b = false;
|
||||
// }
|
||||
|
||||
auto* v = Var("a", nullptr, Expr(false));
|
||||
auto* l = Const("b", nullptr, Expr(false));
|
||||
auto* fa = Func("a", {}, ty.void_(), {Decl(v)});
|
||||
auto* fb = Func("b", {}, ty.void_(), {Decl(l)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
auto* func_a = Sem().Get(fa);
|
||||
auto* func_b = Sem().Get(fb);
|
||||
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
ASSERT_NE(func_a, nullptr);
|
||||
ASSERT_NE(func_b, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), func_a);
|
||||
EXPECT_EQ(local_l->Shadows(), func_b);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsGlobalVar) {
|
||||
// var<private> a : i32;
|
||||
//
|
||||
// fn X() {
|
||||
// var a = a;
|
||||
// }
|
||||
//
|
||||
// fn Y() {
|
||||
// let a = a;
|
||||
// }
|
||||
|
||||
auto* g = Global("a", ty.i32(), ast::StorageClass::kPrivate);
|
||||
auto* v = Var("a", nullptr, Expr("a"));
|
||||
auto* l = Const("a", nullptr, Expr("a"));
|
||||
Func("X", {}, ty.void_(), {Decl(v)});
|
||||
Func("Y", {}, ty.void_(), {Decl(l)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* global = Sem().Get(g);
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), global);
|
||||
EXPECT_EQ(local_l->Shadows(), global);
|
||||
|
||||
auto* user_v =
|
||||
Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
|
||||
auto* user_l =
|
||||
Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
|
||||
|
||||
ASSERT_NE(user_v, nullptr);
|
||||
ASSERT_NE(user_l, nullptr);
|
||||
|
||||
EXPECT_EQ(user_v->Variable(), global);
|
||||
EXPECT_EQ(user_l->Variable(), global);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsGlobalLet) {
|
||||
// let a : i32 = 1;
|
||||
//
|
||||
// fn X() {
|
||||
// var a = (a == 123);
|
||||
// }
|
||||
//
|
||||
// fn Y() {
|
||||
// let a = (a == 321);
|
||||
// }
|
||||
|
||||
auto* g = GlobalConst("a", ty.i32(), Expr(1));
|
||||
auto* v = Var("a", nullptr, Expr("a"));
|
||||
auto* l = Const("a", nullptr, Expr("a"));
|
||||
Func("X", {}, ty.void_(), {Decl(v)});
|
||||
Func("Y", {}, ty.void_(), {Decl(l)});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* global = Sem().Get(g);
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), global);
|
||||
EXPECT_EQ(local_l->Shadows(), global);
|
||||
|
||||
auto* user_v =
|
||||
Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
|
||||
auto* user_l =
|
||||
Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
|
||||
|
||||
ASSERT_NE(user_v, nullptr);
|
||||
ASSERT_NE(user_l, nullptr);
|
||||
|
||||
EXPECT_EQ(user_v->Variable(), global);
|
||||
EXPECT_EQ(user_l->Variable(), global);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsLocalVar) {
|
||||
// fn X() {
|
||||
// var a : i32;
|
||||
// {
|
||||
// var a = a;
|
||||
// }
|
||||
// {
|
||||
// let a = a;
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* s = Var("a", ty.i32(), Expr(1));
|
||||
auto* v = Var("a", nullptr, Expr("a"));
|
||||
auto* l = Const("a", nullptr, Expr("a"));
|
||||
Func("X", {}, ty.void_(), {Decl(s), Block(Decl(v)), Block(Decl(l))});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* local_s = Sem().Get<sem::LocalVariable>(s);
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
|
||||
ASSERT_NE(local_s, nullptr);
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), local_s);
|
||||
EXPECT_EQ(local_l->Shadows(), local_s);
|
||||
|
||||
auto* user_v =
|
||||
Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
|
||||
auto* user_l =
|
||||
Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
|
||||
|
||||
ASSERT_NE(user_v, nullptr);
|
||||
ASSERT_NE(user_l, nullptr);
|
||||
|
||||
EXPECT_EQ(user_v->Variable(), local_s);
|
||||
EXPECT_EQ(user_l->Variable(), local_s);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsLocalLet) {
|
||||
// fn X() {
|
||||
// let a = 1;
|
||||
// {
|
||||
// var a = (a == 123);
|
||||
// }
|
||||
// {
|
||||
// let a = (a == 321);
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* s = Const("a", ty.i32(), Expr(1));
|
||||
auto* v = Var("a", nullptr, Expr("a"));
|
||||
auto* l = Const("a", nullptr, Expr("a"));
|
||||
Func("X", {}, ty.void_(), {Decl(s), Block(Decl(v)), Block(Decl(l))});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* local_s = Sem().Get<sem::LocalVariable>(s);
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
|
||||
ASSERT_NE(local_s, nullptr);
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), local_s);
|
||||
EXPECT_EQ(local_l->Shadows(), local_s);
|
||||
|
||||
auto* user_v =
|
||||
Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
|
||||
auto* user_l =
|
||||
Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
|
||||
|
||||
ASSERT_NE(user_v, nullptr);
|
||||
ASSERT_NE(user_l, nullptr);
|
||||
|
||||
EXPECT_EQ(user_v->Variable(), local_s);
|
||||
EXPECT_EQ(user_l->Variable(), local_s);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, LocalShadowsParam) {
|
||||
// fn F(a : i32) {
|
||||
// {
|
||||
// var a = a;
|
||||
// }
|
||||
// {
|
||||
// let a = a;
|
||||
// }
|
||||
// }
|
||||
|
||||
auto* p = Param("a", ty.i32());
|
||||
auto* v = Var("a", nullptr, Expr("a"));
|
||||
auto* l = Const("a", nullptr, Expr("a"));
|
||||
Func("X", {p}, ty.void_(), {Block(Decl(v)), Block(Decl(l))});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* param = Sem().Get<sem::Parameter>(p);
|
||||
auto* local_v = Sem().Get<sem::LocalVariable>(v);
|
||||
auto* local_l = Sem().Get<sem::LocalVariable>(l);
|
||||
|
||||
ASSERT_NE(param, nullptr);
|
||||
ASSERT_NE(local_v, nullptr);
|
||||
ASSERT_NE(local_l, nullptr);
|
||||
|
||||
EXPECT_EQ(local_v->Shadows(), param);
|
||||
EXPECT_EQ(local_l->Shadows(), param);
|
||||
|
||||
auto* user_v =
|
||||
Sem().Get<sem::VariableUser>(local_v->Declaration()->constructor);
|
||||
auto* user_l =
|
||||
Sem().Get<sem::VariableUser>(local_l->Declaration()->constructor);
|
||||
|
||||
ASSERT_NE(user_v, nullptr);
|
||||
ASSERT_NE(user_l, nullptr);
|
||||
|
||||
EXPECT_EQ(user_v->Variable(), param);
|
||||
EXPECT_EQ(user_l->Variable(), param);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, ParamShadowsFunction) {
|
||||
// fn a(a : bool) {
|
||||
// }
|
||||
|
||||
auto* p = Param("a", ty.bool_());
|
||||
auto* f = Func("a", {p}, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* func = Sem().Get(f);
|
||||
auto* param = Sem().Get<sem::Parameter>(p);
|
||||
|
||||
ASSERT_NE(func, nullptr);
|
||||
ASSERT_NE(param, nullptr);
|
||||
|
||||
EXPECT_EQ(param->Shadows(), func);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, ParamShadowsGlobalVar) {
|
||||
// var<private> a : i32;
|
||||
//
|
||||
// fn F(a : bool) {
|
||||
// }
|
||||
|
||||
auto* g = Global("a", ty.i32(), ast::StorageClass::kPrivate);
|
||||
auto* p = Param("a", ty.bool_());
|
||||
Func("F", {p}, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* global = Sem().Get(g);
|
||||
auto* param = Sem().Get<sem::Parameter>(p);
|
||||
|
||||
ASSERT_NE(global, nullptr);
|
||||
ASSERT_NE(param, nullptr);
|
||||
|
||||
EXPECT_EQ(param->Shadows(), global);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, ParamShadowsGlobalLet) {
|
||||
// let a : i32 = 1;
|
||||
//
|
||||
// fn F(a : bool) {
|
||||
// }
|
||||
|
||||
auto* g = GlobalConst("a", ty.i32(), Expr(1));
|
||||
auto* p = Param("a", ty.bool_());
|
||||
Func("F", {p}, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* global = Sem().Get(g);
|
||||
auto* param = Sem().Get<sem::Parameter>(p);
|
||||
|
||||
ASSERT_NE(global, nullptr);
|
||||
ASSERT_NE(param, nullptr);
|
||||
|
||||
EXPECT_EQ(param->Shadows(), global);
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetTest, ParamShadowsAlias) {
|
||||
// type a = i32;
|
||||
//
|
||||
// fn F(a : a) {
|
||||
// }
|
||||
|
||||
auto* a = Alias("a", ty.i32());
|
||||
auto* p = Param("a", ty.type_name("a"));
|
||||
Func("F", {p}, ty.void_(), {});
|
||||
|
||||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
|
||||
auto* alias = Sem().Get(a);
|
||||
auto* param = Sem().Get<sem::Parameter>(p);
|
||||
|
||||
ASSERT_NE(alias, nullptr);
|
||||
ASSERT_NE(param, nullptr);
|
||||
|
||||
EXPECT_EQ(param->Shadows(), alias);
|
||||
EXPECT_EQ(param->Type(), alias);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
352
src/tint/resolver/var_let_validation_test.cc
Normal file
352
src/tint/resolver/var_let_validation_test.cc
Normal file
@@ -0,0 +1,352 @@
|
||||
// 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/tint/ast/struct_block_attribute.h"
|
||||
#include "src/tint/resolver/resolver.h"
|
||||
#include "src/tint/resolver/resolver_test_helper.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
|
||||
namespace tint {
|
||||
namespace resolver {
|
||||
namespace {
|
||||
|
||||
struct ResolverVarLetValidationTest : public resolver::TestHelper,
|
||||
public testing::Test {};
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, LetNoInitializer) {
|
||||
// let a : i32;
|
||||
WrapInFunction(Const(Source{{12, 34}}, "a", ty.i32(), nullptr));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: let declaration must have an initializer");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, GlobalLetNoInitializer) {
|
||||
// let a : i32;
|
||||
GlobalConst(Source{{12, 34}}, "a", ty.i32(), nullptr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: let declaration must have an initializer");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VarNoInitializerNoType) {
|
||||
// var a;
|
||||
WrapInFunction(Var(Source{{12, 34}}, "a", nullptr));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function scope var declaration requires a type or "
|
||||
"initializer");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, GlobalVarNoInitializerNoType) {
|
||||
// var a;
|
||||
Global(Source{{12, 34}}, "a", nullptr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: module scope var declaration requires a type and "
|
||||
"initializer");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VarTypeNotStorable) {
|
||||
// var i : i32;
|
||||
// var p : pointer<function, i32> = &v;
|
||||
auto* i = Var("i", ty.i32(), ast::StorageClass::kNone);
|
||||
auto* p =
|
||||
Var(Source{{56, 78}}, "a", ty.pointer<i32>(ast::StorageClass::kFunction),
|
||||
ast::StorageClass::kNone, AddressOf(Source{{12, 34}}, "i"));
|
||||
WrapInFunction(i, p);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: ptr<function, i32, read_write> cannot be used as the "
|
||||
"type of a var");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, LetTypeNotConstructible) {
|
||||
// @group(0) @binding(0) var t1 : texture_2d<f32>;
|
||||
// let t2 : t1;
|
||||
auto* t1 =
|
||||
Global("t1", ty.sampled_texture(ast::TextureDimension::k2d, ty.f32()),
|
||||
GroupAndBinding(0, 0));
|
||||
auto* t2 = Const(Source{{56, 78}}, "t2", nullptr, Expr(t1));
|
||||
WrapInFunction(t2);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"56:78 error: texture_2d<f32> cannot be used as the type of a let");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, LetConstructorWrongType) {
|
||||
// var v : i32 = 2u
|
||||
WrapInFunction(Const(Source{{3, 3}}, "v", ty.i32(), Expr(2u)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(3:3 error: cannot initialize let of type 'i32' with value of type 'u32')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VarConstructorWrongType) {
|
||||
// var v : i32 = 2u
|
||||
WrapInFunction(
|
||||
Var(Source{{3, 3}}, "v", ty.i32(), ast::StorageClass::kNone, Expr(2u)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(3:3 error: cannot initialize var of type 'i32' with value of type 'u32')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, LetConstructorWrongTypeViaAlias) {
|
||||
auto* a = Alias("I32", ty.i32());
|
||||
WrapInFunction(Const(Source{{3, 3}}, "v", ty.Of(a), Expr(2u)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(3:3 error: cannot initialize let of type 'i32' with value of type 'u32')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VarConstructorWrongTypeViaAlias) {
|
||||
auto* a = Alias("I32", ty.i32());
|
||||
WrapInFunction(
|
||||
Var(Source{{3, 3}}, "v", ty.Of(a), ast::StorageClass::kNone, Expr(2u)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(3:3 error: cannot initialize var of type 'i32' with value of type 'u32')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, LetOfPtrConstructedWithRef) {
|
||||
// var a : f32;
|
||||
// let b : ptr<function,f32> = a;
|
||||
const auto priv = ast::StorageClass::kFunction;
|
||||
auto* var_a = Var("a", ty.f32(), priv);
|
||||
auto* var_b =
|
||||
Const(Source{{12, 34}}, "b", ty.pointer<float>(priv), Expr("a"), {});
|
||||
WrapInFunction(var_a, var_b);
|
||||
|
||||
ASSERT_FALSE(r()->Resolve());
|
||||
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: cannot initialize let of type 'ptr<function, f32, read_write>' with value of type 'f32')");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, LocalLetRedeclared) {
|
||||
// let l : f32 = 1.;
|
||||
// let l : i32 = 0;
|
||||
auto* l1 = Const("l", ty.f32(), Expr(1.f));
|
||||
auto* l2 = Const(Source{{12, 34}}, "l", ty.i32(), Expr(0));
|
||||
WrapInFunction(l1, l2);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
"12:34 error: redeclaration of 'l'\nnote: 'l' previously declared here");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, GlobalVarRedeclaredAsLocal) {
|
||||
// var v : f32 = 2.1;
|
||||
// fn my_func() {
|
||||
// var v : f32 = 2.0;
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
Global("v", ty.f32(), ast::StorageClass::kPrivate, Expr(2.1f));
|
||||
|
||||
WrapInFunction(Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone,
|
||||
Expr(2.0f)));
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VarRedeclaredInInnerBlock) {
|
||||
// {
|
||||
// var v : f32;
|
||||
// { var v : f32; }
|
||||
// }
|
||||
auto* var_outer = Var("v", ty.f32(), ast::StorageClass::kNone);
|
||||
auto* var_inner =
|
||||
Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone);
|
||||
auto* inner = Block(Decl(var_inner));
|
||||
auto* outer_body = Block(Decl(var_outer), inner);
|
||||
|
||||
WrapInFunction(outer_body);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VarRedeclaredInIfBlock) {
|
||||
// {
|
||||
// var v : f32 = 3.14;
|
||||
// if (true) { var v : f32 = 2.0; }
|
||||
// }
|
||||
auto* var_a_float = Var("v", ty.f32(), ast::StorageClass::kNone, Expr(3.1f));
|
||||
|
||||
auto* var = Var(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kNone,
|
||||
Expr(2.0f));
|
||||
|
||||
auto* cond = Expr(true);
|
||||
auto* body = Block(Decl(var));
|
||||
|
||||
auto* outer_body =
|
||||
Block(Decl(var_a_float),
|
||||
create<ast::IfStatement>(cond, body, ast::ElseStatementList{}));
|
||||
|
||||
WrapInFunction(outer_body);
|
||||
|
||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, InferredPtrStorageAccessMismatch) {
|
||||
// struct Inner {
|
||||
// arr: array<i32, 4>;
|
||||
// }
|
||||
// [[block]] struct S {
|
||||
// inner: Inner;
|
||||
// }
|
||||
// @group(0) @binding(0) var<storage> s : S;
|
||||
// fn f() {
|
||||
// let p : pointer<storage, i32, read_write> = &s.inner.arr[2];
|
||||
// }
|
||||
auto* inner = Structure("Inner", {Member("arr", ty.array<i32, 4>())});
|
||||
auto* buf = Structure("S", {Member("inner", ty.Of(inner))},
|
||||
{create<ast::StructBlockAttribute>()});
|
||||
auto* storage = Global("s", ty.Of(buf), ast::StorageClass::kStorage,
|
||||
ast::AttributeList{
|
||||
create<ast::BindingAttribute>(0),
|
||||
create<ast::GroupAttribute>(0),
|
||||
});
|
||||
|
||||
auto* expr =
|
||||
IndexAccessor(MemberAccessor(MemberAccessor(storage, "inner"), "arr"), 4);
|
||||
auto* ptr = Const(
|
||||
Source{{12, 34}}, "p",
|
||||
ty.pointer<i32>(ast::StorageClass::kStorage, ast::Access::kReadWrite),
|
||||
AddressOf(expr));
|
||||
|
||||
WrapInFunction(ptr);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: cannot initialize let of type "
|
||||
"'ptr<storage, i32, read_write>' with value of type "
|
||||
"'ptr<storage, i32, read>'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, NonConstructibleType_Atomic) {
|
||||
auto* v = Var("v", ty.atomic(Source{{12, 34}}, ty.i32()));
|
||||
WrapInFunction(v);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function variable must have a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, NonConstructibleType_RuntimeArray) {
|
||||
auto* s = Structure("S", {Member(Source{{56, 78}}, "m", ty.array(ty.i32()))},
|
||||
{StructBlock()});
|
||||
auto* v = Var(Source{{12, 34}}, "v", ty.Of(s));
|
||||
WrapInFunction(v);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(
|
||||
r()->error(),
|
||||
R"(12:34 error: runtime-sized arrays can only be used in the <storage> storage class
|
||||
56:78 note: while analysing structure member S.m
|
||||
12:34 note: while instantiating variable v)");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, NonConstructibleType_Struct_WithAtomic) {
|
||||
auto* s = Structure("S", {Member("m", ty.atomic(ty.i32()))});
|
||||
auto* v = Var("v", ty.Of(s));
|
||||
WrapInFunction(v);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"error: function variable must have a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, NonConstructibleType_InferredType) {
|
||||
// @group(0) @binding(0) var s : sampler;
|
||||
// fn foo() {
|
||||
// var v = s;
|
||||
// }
|
||||
Global("s", ty.sampler(ast::SamplerKind::kSampler), GroupAndBinding(0, 0));
|
||||
auto* v = Var(Source{{12, 34}}, "v", nullptr, Expr("s"));
|
||||
WrapInFunction(v);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: function variable must have a constructible type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, InvalidStorageClassForInitializer) {
|
||||
// var<workgroup> v : f32 = 1.23;
|
||||
Global(Source{{12, 34}}, "v", ty.f32(), ast::StorageClass::kWorkgroup,
|
||||
Expr(1.23f));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: var of storage class 'workgroup' cannot have "
|
||||
"an initializer. var initializers are only supported for the "
|
||||
"storage classes 'private' and 'function'");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VectorLetNoType) {
|
||||
// let a : mat3x3 = mat3x3<f32>();
|
||||
WrapInFunction(Const("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3),
|
||||
vec3<f32>()));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, VectorVarNoType) {
|
||||
// var a : mat3x3;
|
||||
WrapInFunction(Var("a", create<ast::Vector>(Source{{12, 34}}, nullptr, 3)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing vector element type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, MatrixLetNoType) {
|
||||
// let a : mat3x3 = mat3x3<f32>();
|
||||
WrapInFunction(Const("a",
|
||||
create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3),
|
||||
mat3x3<f32>()));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
|
||||
}
|
||||
|
||||
TEST_F(ResolverVarLetValidationTest, MatrixVarNoType) {
|
||||
// var a : mat3x3;
|
||||
WrapInFunction(
|
||||
Var("a", create<ast::Matrix>(Source{{12, 34}}, nullptr, 3, 3)));
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(), "12:34 error: missing matrix element type");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
Reference in New Issue
Block a user