blob: 12b33e554d543f1df4040aaa37a409cd6fc782d0 [file] [log] [blame]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/wgsl/resolver/resolver.h"
#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"
#include "src/tint/lang/wgsl/sem/builtin_fn.h"
#include "src/tint/lang/wgsl/sem/value_constructor.h"
#include "gmock/gmock.h"
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
namespace tint::resolver {
namespace {
using ResolverBufferTest = ResolverTest;
TEST_F(ResolverBufferTest, UnsizedBuffer) {
auto* alias = Alias("b", ty("buffer"));
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* b = TypeOf(alias)->UnwrapRef()->As<core::type::Buffer>();
ASSERT_NE(b, nullptr);
EXPECT_EQ(b->Size(), 0u);
}
TEST_F(ResolverBufferTest, SizedBuffer) {
auto* alias = Alias("b", ty("buffer", 16_u));
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* b = TypeOf(alias)->UnwrapRef()->As<core::type::Buffer>();
ASSERT_NE(b, nullptr);
EXPECT_EQ(b->Size(), 16u);
}
TEST_F(ResolverBufferTest, SizedBufferNegative) {
Alias("b", ty("buffer", -1_i));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: buffer size (-1) must be greater than 0)");
}
TEST_F(ResolverBufferTest, SizedBufferZero) {
Alias("b", ty("buffer", 0_i));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: buffer size (0) must be greater than 0)");
}
TEST_F(ResolverBufferTest, EquivalentTypes) {
auto* a1 = Alias("b1", ty("buffer"));
auto* a2 = Alias("b2", ty("buffer"));
auto* a3 = Alias("b3", ty("buffer", 4_i));
auto* a4 = Alias("b4", ty("buffer", 16_u));
auto* a5 = Alias("b5", ty("buffer", 16_i));
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* b1 = TypeOf(a1)->UnwrapRef()->As<core::type::Buffer>();
auto* b2 = TypeOf(a2)->UnwrapRef()->As<core::type::Buffer>();
auto* b3 = TypeOf(a3)->UnwrapRef()->As<core::type::Buffer>();
auto* b4 = TypeOf(a4)->UnwrapRef()->As<core::type::Buffer>();
auto* b5 = TypeOf(a5)->UnwrapRef()->As<core::type::Buffer>();
EXPECT_EQ(b1, b2);
EXPECT_NE(b1, b3);
EXPECT_EQ(b4, b5);
}
TEST_F(ResolverBufferTest, Struct) {
Structure("S", Vector{Member("a", ty("buffer", 16_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(error: buffer<16> cannot be used as the type of a structure member)");
}
TEST_F(ResolverBufferTest, Array) {
Alias("b", ty("array", ty("buffer"), 4_u));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: buffer cannot be used as an element type of an array)");
}
TEST_F(ResolverBufferTest, Pointer_Function) {
Alias("p", ty.ptr<function>(ty("buffer")));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: 'buffer' variables must have 'storage', 'uniform', or 'workgroup' address space)");
}
TEST_F(ResolverBufferTest, Pointer_Private) {
Alias("p", ty.ptr<private_>(ty("buffer")));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: 'buffer' variables must have 'storage', 'uniform', or 'workgroup' address space)");
}
TEST_F(ResolverBufferTest, Pointer_Storage) {
Alias("p", ty.ptr<storage>(ty("buffer")));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, Pointer_Uniform) {
Alias("p", ty.ptr<uniform>(ty("buffer", 16_u)));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, Pointer_Workgroup) {
Alias("p", ty.ptr<workgroup>(ty("buffer", 16_u)));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, Var_Function) {
Func("foo", Empty, ty.void_(),
Vector{
Decl(Var("v", function, ty("buffer", 16_u))),
});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(error: buffer types cannot be declared in the 'function' address space
error: function-scope 'var' must have a constructible type)");
}
TEST_F(ResolverBufferTest, Var_Private) {
GlobalVar("v", private_, ty("buffer", 16_u));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(error: buffer types cannot be declared in the 'private' address space
error: 'buffer' variables must have 'storage', 'uniform', or 'workgroup' address space)");
}
TEST_F(ResolverBufferTest, Var_Storage) {
GlobalVar("v", storage, ty("buffer"), Group(0_a), Binding(0_a));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, Var_Storage_Override) {
Override("o", Expr(4_i));
GlobalVar("v", storage, ty("buffer", Expr(Ident("o"))), Group(0_a), Binding(0_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: buffer type must not be sized with an override-expression in 'storage' address space)");
}
TEST_F(ResolverBufferTest, Var_Uniform) {
GlobalVar("v", uniform, ty("buffer", 16_u), Group(0_a), Binding(0_a));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, Var_Uniform_Unsized) {
GlobalVar("v", uniform, ty("buffer"), Group(0_a), Binding(0_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: buffer type must be sized in 'uniform' address space)");
}
TEST_F(ResolverBufferTest, Var_Uniform_Override) {
Override("o", Expr(4_i));
GlobalVar("v", uniform, ty("buffer", Expr(Ident("o"))), Group(0_a), Binding(0_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: buffer type must not be sized with an override-expression in 'uniform' address space)");
}
TEST_F(ResolverBufferTest, Var_Workgroup) {
GlobalVar("v", workgroup, ty("buffer", 16_u));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, Var_Workgroup_Override) {
Override("o", Expr(4_i));
GlobalVar("v", workgroup, ty("buffer", Expr(Ident("o"))));
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, Var_Workgroup_Unsized) {
GlobalVar("v", workgroup, ty("buffer"), Group(0_a), Binding(0_a));
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: buffer type must be sized in 'workgroup' address space)");
}
TEST_F(ResolverBufferTest, FunctionParameter) {
Func("foo", Vector{Param("b", ty("buffer"))}, ty.void_(), Empty);
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(error: buffer types cannot be declared in the 'undefined' address space
error: type of function parameter cannot be buffer)");
}
TEST_F(ResolverBufferTest, FunctionParameter_Pointer) {
Func("foo", Vector{Param("b", ty.ptr<storage>(ty("buffer")))}, ty.void_(), Empty);
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, FunctionParameter_SizedMatchesUnsized) {
Func("foo", Vector{Param("p", ty.ptr<storage>(ty("buffer")))}, ty.void_(), Empty);
auto* v = GlobalVar("v", storage, ty("buffer", 16_u), Group(0_a), Binding(0_a));
Func("bar", Empty, ty.void_(), Vector{CallStmt(Call(Ident("foo"), AddressOf(v)))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, FunctionParameter_LargerSizedMatchesSmallerSized) {
Func("foo", Vector{Param("p", ty.ptr<storage>(ty("buffer", 8_i)))}, ty.void_(), Empty);
auto* v = GlobalVar("v", storage, ty("buffer", 16_u), Group(0_a), Binding(0_a));
Func("bar", Empty, ty.void_(), Vector{CallStmt(Call(Ident("foo"), AddressOf(v)))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferTest, FunctionParameter_UnsizedDoesNotMatchSized) {
Func("foo", Vector{Param("p", ty.ptr<storage>(ty("buffer", 16_u)))}, ty.void_(), Empty);
auto* v = GlobalVar("v", storage, ty("buffer"), Group(0_a), Binding(0_a));
Func("bar", Empty, ty.void_(), Vector{CallStmt(Call(Ident("foo"), AddressOf(v)))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: type mismatch for argument 1 in call to 'foo', expected 'ptr<storage, buffer<16>, read>', got 'ptr<storage, buffer, read>')");
}
using ResolverBufferViewTest = ResolverTest;
TEST_F(ResolverBufferViewTest, Storage_Unsized) {
auto* gv = GlobalVar("v", storage, ty("buffer"), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(),
Call(Ident("bufferView", ty("array", ty.u32())), AddressOf(gv), 0_u))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferViewTest, Storage_Sized) {
auto* gv = GlobalVar("v", storage, ty("buffer", 16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(),
Call(Ident("bufferView", ty("array", ty.u32())), AddressOf(gv), 0_u))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferViewTest, Uniform_Sized) {
auto* gv = GlobalVar("v", uniform, ty("buffer", 64_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(),
Call(Ident("bufferView", ty("array", ty.u32(), 4_u)), AddressOf(gv), 0_u))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferViewTest, Workgroup_Sized) {
auto* gv = GlobalVar("v", workgroup, ty("buffer", 64_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(),
Call(Ident("bufferView", ty("array", ty.u32(), 4_u)), AddressOf(gv), 0_u))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferViewTest, ReturnTypeContainsAtomic) {
Structure("S", Vector{Member("a", ty("array", ty("atomic", ty.u32()), 4_u))});
auto* gv = GlobalVar("v", storage, ty("buffer"), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", Expr(Ident("S"))), AddressOf(gv), 0_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: return type of bufferView cannot contain an atomic type)");
}
TEST_F(ResolverBufferViewTest, Offset_Unsigned_TooSmall) {
auto* gv = GlobalVar("v", storage, ty("buffer", 16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", ty.u32()), AddressOf(gv), 20_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the offset argument of bufferView plus the size of the return type must be smaller than the buffer size)");
}
TEST_F(ResolverBufferViewTest, Offset_Unsigned_Unaligned) {
auto* gv = GlobalVar("v", storage, ty("buffer", 16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", ty.u32()), AddressOf(gv), 1_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the offset argument of bufferView must evenly divide the alignment of the return type (4))");
}
TEST_F(ResolverBufferViewTest, Offset_Signed_TooSmall) {
auto* gv = GlobalVar("v", storage, ty("buffer", 16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", ty.u32()), AddressOf(gv), 20_i))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the offset argument of bufferView plus the size of the return type must be smaller than the buffer size)");
}
TEST_F(ResolverBufferViewTest, Offset_Signed_Unaligned) {
auto* gv = GlobalVar("v", storage, ty("buffer", 16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", ty.u32()), AddressOf(gv), 1_a))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the offset argument of bufferView must evenly divide the alignment of the return type (4))");
}
TEST_F(ResolverBufferViewTest, Offset_Signed_Negative) {
auto* gv = GlobalVar("v", storage, ty("buffer", 16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", ty.u32()), AddressOf(gv), -1_i))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: the offset argument of bufferView must be non-negative)");
}
TEST_F(ResolverBufferViewTest, Return_Buffer_Unsized) {
auto* gv = GlobalVar("v", storage, ty("buffer"), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", ty("buffer")), AddressOf(gv), 0_i))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: return type of bufferView cannot be a buffer)");
}
TEST_F(ResolverBufferViewTest, Return_Buffer_Sized) {
auto* gv = GlobalVar("v", storage, ty("buffer"), Group(0_a), Binding(0_a));
Func(
"foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", ty("buffer", 16_u)), AddressOf(gv), 0_i))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: return type of bufferView cannot be a buffer)");
}
TEST_F(ResolverBufferViewTest, Return_NonHostShareable) {
Override("o", ty.u32());
auto* gv = GlobalVar("v", storage, ty("buffer"), Group(0_a), Binding(0_a));
auto array = ty("array", ty.u32(), Expr(Ident("o")));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferView", array), AddressOf(gv), 0_i))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(error: override-sized arrays can only be used in the <workgroup> address space
note: while instantiating bufferView)");
}
} // namespace
} // namespace tint::resolver