blob: f935b42523d75185c850ffb63d5301c5b450e028 [file]
// 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 "gmock/gmock.h"
#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"
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.AsType("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.AsType("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
note: while instantiating 'var' v)");
}
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
note: while instantiating 'var' v)");
}
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.AsType("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: variables in 'uniform' address space must have a fixed footprint)");
}
TEST_F(ResolverBufferTest, Var_Uniform_Override) {
Override("o", Expr(4_i));
GlobalVar("v", uniform, ty.AsType("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.AsType("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: variables in 'workgroup' address space must have a fixed footprint)");
}
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
note: while instantiating parameter b)");
}
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)");
}
TEST_F(ResolverBufferViewTest, Offset_Overflow) {
ExpectError(
R"(
@group(0) @binding(0) var<storage, read_write> v : buffer<24>;
fn foo() {
_ = bufferView<vec4u>(&v, 4294967280u);
}
)",
R"(
input.wgsl:4:29 error: the offset argument of bufferView plus the size of the return type must not overflow a 32-bit unsigned integer
_ = bufferView<vec4u>(&v, 4294967280u);
^^^^^^^^^^^
)");
}
TEST_F(ResolverBufferViewTest, Variable_TooSmall_ThroughFunction) {
ExpectError(
R"(
@group(0) @binding(0) var<storage, read_write> v : buffer<64>;
fn foo(p : ptr<storage, buffer, read_write>) {
_ = bufferView<u32>(p, 64);
}
fn bar() {
foo(&v);
}
)",
R"(
input.wgsl:2:23 error: buffer size (64 bytes) is smaller than the minimum view size (68 bytes)
@group(0) @binding(0) var<storage, read_write> v : buffer<64>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
input.wgsl:4:7 note: due to call here
_ = bufferView<u32>(p, 64);
^^^^^^^^^^^^^^^^^^^^^^
)");
}
TEST_F(ResolverBufferViewTest, Parameter_TooSmall_ThroughFunction) {
ExpectError(
R"(
fn foo(p : ptr<storage, buffer, read_write>) {
_ = bufferView<u32>(p, 64);
}
fn bar(p : ptr<storage, buffer<64>, read_write>) {
foo(p);
}
)",
R"(
input.wgsl:5:8 error: buffer size (64 bytes) is smaller than the minimum view size (68 bytes)
fn bar(p : ptr<storage, buffer<64>, read_write>) {
^
input.wgsl:3:7 note: due to call here
_ = bufferView<u32>(p, 64);
^^^^^^^^^^^^^^^^^^^^^^
)");
}
using ResolverBufferArrayViewTest = ResolverTest;
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
0_u, 4_u))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferArrayViewTest, Uniform_Unsized) {
auto* gv = GlobalVar("v", uniform, ty.buffer(16_u), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
0_u, 4_u))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferArrayViewTest, Workgroup_Unsized) {
auto* gv = GlobalVar("v", storage, ty.buffer(16_u), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
0_u, 4_u))});
EXPECT_TRUE(r()->Resolve()) << r()->error();
}
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.array(ty.u32(), 4_u)),
AddressOf(gv), 0_u, 16_u))});
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
R"(error: return type of bufferArrayView cannot have a fixed footprint)");
}
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.array(ty.u32(), 4_u)),
AddressOf(gv), 0_u, 16_u))});
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
R"(error: return type of bufferArrayView cannot have a fixed footprint)");
}
TEST_F(ResolverBufferArrayViewTest, Workgroup_Sized) {
auto* gv = GlobalVar("v", workgroup, ty.buffer(64_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(), Call(Ident("bufferArrayView", ty.array(ty.u32(), 4_u)),
AddressOf(gv), 0_u, 16_u))});
EXPECT_FALSE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(),
R"(error: return type of bufferArrayView cannot have a fixed footprint)");
}
TEST_F(ResolverBufferArrayViewTest, ReturnTypeContainsAtomic) {
Structure("S", Vector{Member("a", ty.array(ty.atomic(ty.u32())))});
auto* gv = GlobalVar("v", storage, ty.buffer(), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(),
Call(Ident("bufferArrayView", Expr(Ident("S"))), AddressOf(gv), 0_u, 4_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(error: return type of bufferArrayView cannot contain an atomic type)");
}
TEST_F(ResolverBufferArrayViewTest, Offset_Unsigned_TooSmall) {
auto* gv = GlobalVar("v", storage, ty.buffer(16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Decl(Let("a", ty.u32(), Expr(4_u))),
Assign(Phony(), Call(Ident("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
20_u, Ident("a")))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the buffer (16 bytes) must be large enough to include one element of the runtime-sized array (4 bytes) with the given offset (20 bytes))");
}
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
1_u, 4_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the offset argument of bufferArrayView must evenly divide the alignment of the return type (4))");
}
TEST_F(ResolverBufferArrayViewTest, Offset_Signed_TooSmall) {
auto* gv = GlobalVar("v", storage, ty.buffer(16_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Decl(Let("a", ty.u32(), Expr(4_u))),
Assign(Phony(), Call(Ident("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
20_i, Ident("a")))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the buffer (16 bytes) must be large enough to include one element of the runtime-sized array (4 bytes) with the given offset (20 bytes))");
}
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
1_a, 4_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the offset argument of bufferArrayView must evenly divide the alignment of the return type (4))");
}
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.array(ty.u32())), AddressOf(gv),
-1_i, 4_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(),
R"(error: the offset argument of bufferArrayView must be non-negative)");
}
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.buffer()), AddressOf(gv), 0_i, 4_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: return type of bufferArrayView cannot be a buffer)");
}
TEST_F(ResolverBufferArrayViewTest, 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("bufferArrayView", ty.buffer(16_u)), AddressOf(gv), 0_i, 16_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: return type of bufferArrayView cannot be a buffer)");
}
TEST_F(ResolverBufferArrayViewTest, Size_Unsigned_TooSmall) {
Structure("S", Vector{Member("a", ty.vec2(ty.u32())), Member("b", ty.array(ty.u32()))});
auto* gv = GlobalVar("v", storage, ty.buffer(8_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{
Assign(Phony(), Call(Ident("bufferArrayView", Ident("S")), AddressOf(gv), 0_u, 8_u))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the size argument (8 bytes) of bufferArrayView must be large enough to include one element of the runtime-sized array (12 bytes))");
}
TEST_F(ResolverBufferArrayViewTest, Size_Signed_TooSmall) {
Structure("S", Vector{Member("a", ty.vec2(ty.u32())), Member("b", ty.array(ty.u32()))});
auto* gv = GlobalVar("v", storage, ty.buffer(8_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{
Assign(Phony(), Call(Ident("bufferArrayView", Ident("S")), AddressOf(gv), 0_u, 8_i))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the size argument (8 bytes) of bufferArrayView must be large enough to include one element of the runtime-sized array (12 bytes))");
}
TEST_F(ResolverBufferArrayViewTest, Size_Signed_Negative) {
Structure("S", Vector{Member("a", ty.vec2(ty.u32())), Member("b", ty.array(ty.u32()))});
auto* gv = GlobalVar("v", storage, ty.buffer(8_a), Group(0_a), Binding(0_a));
Func("foo", Empty, ty.void_(),
Vector{Assign(Phony(),
Call(Ident("bufferArrayView", Ident("S")), AddressOf(gv), 0_u, -1_a))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(r()->error(), R"(error: the size argument of bufferArrayView must be non-negative)");
}
TEST_F(ResolverBufferArrayViewTest, OffsetAndSize_TooSmall) {
Structure("S", Vector{Member("a", ty.vec2(ty.u32())), Member("b", ty.array(ty.u32()))});
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("bufferArrayView", Ident("S")), AddressOf(gv), 8_u, 12_a))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: the buffer (16 bytes) must be large enough to include one element of the runtime-sized array (12 bytes) with the given offset (8 bytes) and size (12 bytes))");
}
TEST_F(ResolverBufferArrayViewTest, InvalidType) {
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("bufferArrayView", ty.sampler(core::type::SamplerKind::kSampler)),
AddressOf(gv), 8_u, 12_a))});
EXPECT_FALSE(r()->Resolve());
EXPECT_EQ(
r()->error(),
R"(error: type 'sampler' cannot be used in address space 'storage' as it is non-host-shareable
note: while instantiating bufferView)");
}
TEST_F(ResolverBufferArrayViewTest, OffsetAndSize_TooSmall_Vec3) {
ExpectError(
R"(
struct S {
a : vec2<u32>,
b : array<vec3u>,
}
@group(0) @binding(0) var<storage, read_write> v : buffer<24>;
fn foo() {
_ = bufferArrayView<S>(&v, 16, 32);
}
)",
R"(
input.wgsl:8:7 error: the buffer (24 bytes) must be large enough to include one element of the runtime-sized array (32 bytes) with the given offset (16 bytes) and size (32 bytes)
_ = bufferArrayView<S>(&v, 16, 32);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}
TEST_F(ResolverBufferArrayViewTest, OffsetAndSize_Overflow) {
ExpectError(
R"(
struct S {
a : vec2<u32>,
b : array<vec3u>,
}
@group(0) @binding(0) var<storage, read_write> v : buffer<24>;
fn foo() {
_ = bufferArrayView<S>(&v, 2147483648u, 2147483648u);
}
)",
R"(
input.wgsl:8:7 error: the offset and size arguments of bufferArrayView plus the minimum return type size must not overflow a 32-bit unsigned integer
_ = bufferArrayView<S>(&v, 2147483648u, 2147483648u);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}
TEST_F(ResolverBufferArrayViewTest, Size_StrideDivisble) {
ExpectError(
R"(
@group(0) @binding(0) var<storage, read_write> v : buffer;
fn foo() {
_ = bufferArrayView<array<u32>>(&v, 0, 5);
}
)",
R"(
input.wgsl:4:42 error: the size argument (5 bytes) of bufferArrayView minus the return type offset (0 bytes) must be evenly divisible by the stride of the runtime-sized array (4 bytes)
_ = bufferArrayView<array<u32>>(&v, 0, 5);
^
)");
}
TEST_F(ResolverBufferArrayViewTest, Size_StrideDivisbleWithReturnOffset) {
ExpectError(
R"(
struct S {
a : vec2<u32>,
b : array<u32>,
}
@group(0) @binding(0) var<storage, read_write> v : buffer;
fn foo() {
_ = bufferArrayView<S>(&v, 0, 13);
}
)",
R"(
input.wgsl:8:33 error: the size argument (13 bytes) of bufferArrayView minus the return type offset (8 bytes) must be evenly divisible by the stride of the runtime-sized array (4 bytes)
_ = bufferArrayView<S>(&v, 0, 13);
^^
)");
}
TEST_F(ResolverBufferArrayViewTest, Variable_TooSmall_ThroughFunction) {
ExpectError(
R"(
@group(0) @binding(0) var<storage, read_write> v : buffer<64>;
fn foo(p : ptr<storage, buffer, read_write>) {
_ = bufferArrayView<array<u32>>(p, 64, 4);
}
fn bar() {
foo(&v);
}
)",
R"(
input.wgsl:2:23 error: buffer size (64 bytes) is smaller than the minimum view size (68 bytes)
@group(0) @binding(0) var<storage, read_write> v : buffer<64>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
input.wgsl:4:7 note: due to call here
_ = bufferArrayView<array<u32>>(p, 64, 4);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}
TEST_F(ResolverBufferArrayViewTest, Parameter_TooSmall_ThroughFunction) {
ExpectError(
R"(
struct S {
a : vec2u,
b : array<u32>,
}
fn foo(p : ptr<storage, buffer, read_write>) {
_ = bufferArrayView<S>(p, 56, 12);
}
fn bar(p : ptr<storage, buffer<64>, read_write>) {
foo(p);
}
)",
R"(
input.wgsl:9:8 error: buffer size (64 bytes) is smaller than the minimum view size (68 bytes)
fn bar(p : ptr<storage, buffer<64>, read_write>) {
^
input.wgsl:7:7 note: due to call here
_ = bufferArrayView<S>(p, 56, 12);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}
} // namespace
} // namespace tint::resolver