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:
Ryan Harrison
2022-02-21 15:19:07 +00:00
committed by Tint LUCI CQ
parent 38f1e9c75c
commit dbc13af287
12231 changed files with 4897 additions and 4871 deletions

View 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 = *(&param)[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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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_

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View 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