blob: c8564b228c3f2898abc40e68e0305cc20e8c818b [file] [edit]
// Copyright 2025 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/core/ir/validator_test.h"
#include <string>
#include <tuple>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/tint/lang/core/enums.h"
#include "src/tint/lang/core/ir/ir_helper_test.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/core/number.h"
#include "src/tint/lang/core/type/abstract_float.h"
#include "src/tint/lang/core/type/abstract_int.h"
#include "src/tint/lang/core/type/binding_array.h"
#include "src/tint/lang/core/type/clone_context.h"
#include "src/tint/lang/core/type/manager.h"
#include "src/tint/lang/core/type/matrix.h"
#include "src/tint/lang/core/type/memory_view.h"
#include "src/tint/lang/core/type/reference.h"
#include "src/tint/lang/core/type/resource_binding.h"
#include "src/tint/lang/core/type/sampled_texture.h"
#include "src/tint/lang/core/type/storage_texture.h"
namespace tint::mock {
/// A mock non-core type used for testing the non-core type validation rule.
class NonCoreType final : public Castable<NonCoreType, core::type::Type> {
public:
NonCoreType() : Base(0u, core::type::Flags{}) {}
bool Equals(const UniqueNode& other) const override { return other.Is<NonCoreType>(); }
std::string FriendlyName() const override { return "NonCoreType"; }
core::type::Type* Clone(core::type::CloneContext& ctx) const override {
return ctx.dst.mgr->Get<NonCoreType>();
}
};
} // namespace tint::mock
TINT_INSTANTIATE_TYPEINFO(tint::mock::NonCoreType);
namespace tint::core::ir {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
namespace {
template <typename T>
static const core::type::Type* TypeBuilder(core::type::Manager& m) {
return m.Get<T>();
}
template <typename T>
static const core::type::Type* RefTypeBuilder(core::type::Manager& m) {
return m.ref<AddressSpace::kFunction, T>();
}
using TypeBuilderFn = decltype(&TypeBuilder<i32>);
} // namespace
// Note: Just parameterizing abstract int vs float doesn't significantly reduce
// the code size of these tests, and made the code less readable.
//
// Combining them with the non-abstract parameterizing helps a little but adds
// some switching logic to the fixtures since the error strings are different.
// There is also still an issue with needing different fixtures for vec2 vs vec3
// vs vec4, which gets even worst for matrices. There is also variance in what
// is allowed where, so it feels like mostly a wash for code length, and makes
// harder to read code.
//
// There is probably something sophisticated using builders for
// scalar/vector/etc and testing::Combine to parameterize things, but that
// requires handling how the type manager allows templating/dispatch.
TEST_F(IR_ValidatorTest, AbstractFloat_Scalar) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("af", function, ty.AFloat());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
%af:ptr<function, abstract-float, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractInt_Scalar) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("ai", function, ty.AInt());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
%ai:ptr<function, abstract-int, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractFloat_Vector) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("af", function, ty.vec2<AFloat>());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
%af:ptr<function, vec2<abstract-float>, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractInt_Vector) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("ai", function, ty.vec3<AInt>());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(3:5 error: var: abstracts are not permitted
%ai:ptr<function, vec3<abstract-int>, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractFloat_Matrix) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("af", function, ty.mat2x2<AFloat>());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
%af:ptr<function, mat2x2<abstract-float>, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractInt_Matrix) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("ai", function, ty.mat3x4<AInt>());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: abstracts are not permitted
%ai:ptr<function, mat3x4<abstract-int>, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractFloat_Struct) {
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("af"), ty.AFloat(), {}},
});
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:3 error: var: abstracts are not permitted
%1:ptr<private, MyStruct, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractInt_Struct) {
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("ai"), ty.AInt(), {}},
});
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:3 error: var: abstracts are not permitted
%1:ptr<private, MyStruct, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractFloat_FunctionParam) {
auto* f = b.Function("my_func", ty.void_());
f->SetParams({b.FunctionParam(ty.AFloat())});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
%my_func = func(%2:abstract-float):void {
^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, AbstractInt_FunctionParam) {
auto* f = b.Function("my_func", ty.void_());
f->SetParams({b.FunctionParam(ty.AInt())});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:17 error: abstracts are not permitted
%my_func = func(%2:abstract-int):void {
^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_Void) {
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("v"), ty.void_(), {}},
});
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:3 error: var: struct member 0 cannot have void type
%1:ptr<private, MyStruct, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_AlignZero) {
core::IOAttributes attrs = {};
tint::Vector<const core::type::StructMember*, 4> members;
members.Push(ty.Get<core::type::StructMember>(mod.symbols.New("v"), ty.u32(), 0u, 0u,
/* align */ 0u, 16u, std::move(attrs)));
auto* str_ty = ty.Get<core::type::Struct>(mod.symbols.New("MyStruct"), std::move(members),
tint::RoundUp(0u, 16u));
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:3 error: var: struct member must not have an alignment of 0
%1:ptr<private, MyStruct, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_AlignNotDivisibleByTypeAlignment) {
core::IOAttributes attrs = {};
tint::Vector<const core::type::StructMember*, 4> members;
members.Push(ty.Get<core::type::StructMember>(mod.symbols.New("v"), ty.u32(), 0u, 0u,
/* align */ 10u, 16u, std::move(attrs)));
auto* str_ty = ty.Get<core::type::Struct>(mod.symbols.New("MyStruct"), std::move(members),
tint::RoundUp(0u, 16u));
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:6:3 error: var: struct member alignment (10) must be divisible by type alignment (4)
%1:ptr<private, MyStruct, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructureMember_SizeTooSmall) {
auto* str_ty =
ty.Struct(mod.symbols.New("S"),
Vector{
ty.Get<type::StructMember>(mod.symbols.New("a"), ty.array<u32, 3>(), 0u, 0u,
4u, 4u, IOAttributes{}),
});
mod.root_block->Append(b.Var("my_struct", private_, str_ty));
auto* fn = b.Function("F", ty.void_());
b.Append(fn->Block(), [&] { b.Return(fn); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
"struct member 0 with size=4 must be at least as large as the type with size 12"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_RuntimeArrayNotLast) {
auto* s1 = ty.Struct(mod.symbols.New("S1"), {{mod.symbols.New("a"), ty.u32()}});
auto* rta = ty.runtime_array(s1);
auto* str_ty = ty.Struct(mod.symbols.New("OuterS"), {
{mod.symbols.New("a1"), rta},
{mod.symbols.New("j"), ty.u32()},
});
auto* v = b.Var(ty.ptr(storage, str_ty, read_write));
v->SetBindingPoint(0, 0);
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:11:3 error: var: runtime-sized arrays can only be the last member of a struct
%1:ptr<storage, OuterS, read_write> = var undef @binding_point(0, 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^)"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_RuntimeArrayIsLast) {
auto* s1 = ty.Struct(mod.symbols.New("S1"), {{mod.symbols.New("a"), ty.u32()}});
auto* rta = ty.runtime_array(s1);
auto* str_ty = ty.Struct(mod.symbols.New("OuterS"), {
{mod.symbols.New("j"), ty.u32()},
{mod.symbols.New("a1"), rta},
});
auto* v = b.Var(ty.ptr(storage, str_ty, read_write));
v->SetBindingPoint(0, 0);
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_MultipleRuntimeArrays) {
auto* s1 = ty.Struct(mod.symbols.New("S1"), {{mod.symbols.New("a"), ty.u32()}});
auto* rta = ty.runtime_array(s1);
auto* str_ty = ty.Struct(mod.symbols.New("OuterS"), {
{mod.symbols.New("a1"), rta},
{mod.symbols.New("a2"), rta},
});
auto* v = b.Var(ty.ptr(storage, str_ty, read_write));
v->SetBindingPoint(0, 0);
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:11:3 error: var: runtime-sized arrays can only be the last member of a struct
%1:ptr<storage, OuterS, read_write> = var undef @binding_point(0, 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^)"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_RowMajor_WithoutCapability) {
auto* mat_ty = ty.mat2x2<f32>();
auto* member = ty.Get<core::type::StructMember>(
mod.symbols.New("m"), mat_ty, 0u, 0u, mat_ty->Align(), mat_ty->Size(), IOAttributes{});
member->SetRowMajor();
auto* str_ty =
ty.Get<core::type::Struct>(mod.symbols.New("MyStruct"), Vector{member}, mat_ty->Size());
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:6:3 error: var: Row major annotation not allowed on structures
%1:ptr<private, MyStruct, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_RowMajor_WithCapability) {
auto* mat_ty = ty.mat2x2<f32>();
auto* member = ty.Get<core::type::StructMember>(
mod.symbols.New("m"), mat_ty, 0u, 0u, mat_ty->Align(), mat_ty->Size(), IOAttributes{});
member->SetRowMajor();
auto* str_ty =
ty.Get<core::type::Struct>(mod.symbols.New("MyStruct"), Vector{member}, mat_ty->Size());
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod, Capabilities{Capability::kAllowStructMatrixDecorations});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_MatrixStride_WithoutCapability) {
auto* mat_ty = ty.mat2x2<f32>();
auto* member = ty.Get<core::type::StructMember>(
mod.symbols.New("m"), mat_ty, 0u, 0u, mat_ty->Align(), mat_ty->Size(), IOAttributes{});
member->SetMatrixStride(32);
auto* str_ty =
ty.Get<core::type::Struct>(mod.symbols.New("MyStruct"), Vector{member}, mat_ty->Size());
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:6:3 error: var: Matrix stride annotation not allowed on structures
%1:ptr<private, MyStruct, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StructMember_MatrixStride_WithCapability) {
auto* mat_ty = ty.mat2x2<f32>();
auto* member = ty.Get<core::type::StructMember>(
mod.symbols.New("m"), mat_ty, 0u, 0u, mat_ty->Align(), mat_ty->Size(), IOAttributes{});
member->SetMatrixStride(32);
auto* str_ty =
ty.Get<core::type::Struct>(mod.symbols.New("MyStruct"), Vector{member}, mat_ty->Size());
auto* v = b.Var(ty.ptr(private_, str_ty));
mod.root_block->Append(v);
auto res = ir::Validate(mod, Capabilities{Capability::kAllowStructMatrixDecorations});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, FunctionParam_InvalidAddressSpaceForHandleType) {
auto* type = ty.ptr(AddressSpace::kFunction, ty.sampler());
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { //
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr("handle types can only be declared in the 'handle' address space"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, FunctionParam_InvalidTypeForHandleAddressSpace) {
auto* type = ty.ptr(AddressSpace::kHandle, ty.u32());
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { //
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("the 'handle' address space can only be used for handle types"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, FunctionParam_InvalidHandlePointer) {
auto* type =
ty.ptr(AddressSpace::kHandle, ty.sampled_texture(type::TextureDimension::k1d, ty.f32()));
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { //
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("function parameter type, 'ptr<handle, texture_1d<f32>, read>', "
"must be constructible, a pointer, or a handle"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, NonCoreType) {
auto* fn = b.Function("my_func", ty.void_());
fn->AppendParam(b.FunctionParam(ty.Get<tint::mock::NonCoreType>()));
b.Append(fn->Block(), [&] { //
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("non-core types not allowed in core IR"))
<< res.Failure();
}
using TypeTest = IRTestParamHelper<std::tuple<
/* allowed */ bool,
/* type_builder */ TypeBuilderFn>>;
using Type_ArrayElements = TypeTest;
TEST_P(Type_ArrayElements, Test) {
bool allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("v", AddressSpace::kFunction, ty.array(type, 4));
b.Return(f);
});
if (allowed) {
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
} else {
auto res = ir::Validate(mod);
ASSERT_NE(res, Success) << res.Failure();
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: array elements, ')" +
ty.array(type, 4)->FriendlyName() +
R"(', must have creation-fixed footprint
)")) << res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
Type_ArrayElements,
testing::Values(std::make_tuple(true, TypeBuilder<u32>),
std::make_tuple(true, TypeBuilder<i32>),
std::make_tuple(true, TypeBuilder<f32>),
std::make_tuple(true, TypeBuilder<f16>),
std::make_tuple(true, TypeBuilder<core::type::Bool>),
std::make_tuple(false, TypeBuilder<core::type::Void>)));
using Type_VectorElements = TypeTest;
TEST_P(Type_VectorElements, Test) {
bool allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
if (allowed) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("v2", AddressSpace::kFunction, ty.vec2(type));
b.Var("v3", AddressSpace::kFunction, ty.vec3(type));
b.Var("v4", AddressSpace::kFunction, ty.vec4(type));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
} else {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("invalid", AddressSpace::kFunction, ty.vec2(type));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success) << res.Failure();
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: vector elements, ')" +
ty.vec2(type)->FriendlyName() + R"(', must be scalars
)")) << res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
Type_VectorElements,
testing::Values(std::make_tuple(true, TypeBuilder<u32>),
std::make_tuple(true, TypeBuilder<i32>),
std::make_tuple(true, TypeBuilder<f32>),
std::make_tuple(true, TypeBuilder<f16>),
std::make_tuple(true, TypeBuilder<core::type::Bool>),
std::make_tuple(false, TypeBuilder<core::type::Void>)));
using Type_MatrixElements = TypeTest;
TEST_P(Type_MatrixElements, Test) {
bool allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
if (allowed) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("m2x2", AddressSpace::kFunction, ty.mat2x2(type));
b.Var("m2x3", AddressSpace::kFunction, ty.mat2x3(type));
b.Var("m2x4", AddressSpace::kFunction, ty.mat2x4(type));
b.Var("m3x2", AddressSpace::kFunction, ty.mat3x2(type));
b.Var("m3x3", AddressSpace::kFunction, ty.mat3x3(type));
b.Var("m3x4", AddressSpace::kFunction, ty.mat3x4(type));
b.Var("m4x2", AddressSpace::kFunction, ty.mat4x2(type));
b.Var("m4x3", AddressSpace::kFunction, ty.mat4x3(type));
b.Var("m4x4", AddressSpace::kFunction, ty.mat4x4(type));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
} else {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("invalid", AddressSpace::kFunction, ty.mat3x3(type));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success) << res.Failure();
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: matrix elements, ')" +
ty.mat3x3(type)->FriendlyName() + R"(', must be float scalars
)")) << res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
Type_MatrixElements,
testing::Values(std::make_tuple(false, TypeBuilder<u32>),
std::make_tuple(false, TypeBuilder<i32>),
std::make_tuple(true, TypeBuilder<f32>),
std::make_tuple(true, TypeBuilder<f16>),
std::make_tuple(false, TypeBuilder<core::type::Bool>),
std::make_tuple(false, TypeBuilder<core::type::Void>)));
using Type_SubgroupMatrixComponentType = TypeTest;
TEST_P(Type_SubgroupMatrixComponentType, Test) {
bool allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Var("m", AddressSpace::kFunction, ty.subgroup_matrix_result(type, 8u, 8u));
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllow8BitIntegers});
if (allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(":3:5 error: var: invalid subgroup matrix component type: '" +
type->FriendlyName()))
<< res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
Type_SubgroupMatrixComponentType,
testing::Values(std::make_tuple(true, TypeBuilder<f32>),
std::make_tuple(true, TypeBuilder<f16>),
std::make_tuple(true, TypeBuilder<i8>),
std::make_tuple(true, TypeBuilder<i32>),
std::make_tuple(true, TypeBuilder<u8>),
std::make_tuple(true, TypeBuilder<u32>),
std::make_tuple(false, TypeBuilder<core::type::Bool>),
std::make_tuple(false, TypeBuilder<core::type::Void>)));
using Type_SampledTextureSampledType = TypeTest;
TEST_P(Type_SampledTextureSampledType, Test) {
bool allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
b.Append(mod.root_block, [&] {
auto* var = b.Var("m", AddressSpace::kHandle,
ty.sampled_texture(core::type::TextureDimension::k2d, type));
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod);
if (allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(":2:3 error: var: invalid sampled texture sample type: '" +
type->FriendlyName()))
<< res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
Type_SampledTextureSampledType,
testing::Values(std::make_tuple(true, TypeBuilder<f32>),
std::make_tuple(true, TypeBuilder<i32>),
std::make_tuple(true, TypeBuilder<u32>),
std::make_tuple(false, TypeBuilder<f16>),
std::make_tuple(false, TypeBuilder<core::type::Bool>),
std::make_tuple(false, TypeBuilder<core::type::Void>)));
TEST_F(IR_ValidatorTest, BindingArray) {
b.Append(mod.root_block, [&] {
auto* var = b.Var(
"m", AddressSpace::kHandle,
ty.binding_array(ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()), 4));
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success);
}
TEST_F(IR_ValidatorTest, BindingArrayRuntimeCount) {
b.Append(mod.root_block, [&] {
auto* var = b.Var("m", AddressSpace::kHandle,
ty.Get<core::type::BindingArray>(
ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()),
ty.Get<core::type::RuntimeArrayCount>()));
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:2:3 error: var: binding_array count must be a constant expression
%m:ptr<handle, binding_array<texture_2d<f32>, >, read> = var undef @binding_point(0, 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^)"));
}
TEST_F(IR_ValidatorTest, BindingArrayNonSampledTexture) {
b.Append(mod.root_block, [&] {
auto* var = b.Var("m", AddressSpace::kHandle, ty.binding_array(ty.external_texture(), 5));
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:2:3 error: var: binding_array element type must be a sampled texture type
%m:ptr<handle, binding_array<texture_external, 5>, read> = var undef @binding_point(0, 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^)"));
}
TEST_F(IR_ValidatorTest, ResourceBinding_WithoutCapabilityFails) {
b.Append(mod.root_block, [&] {
auto* var = b.Var("m", AddressSpace::kHandle, ty.Get<core::type::ResourceBinding>());
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:2:3 error: var: resource_binding type can only be used with kAllowResourceBinding capability
%m:ptr<handle, resource_binding, read> = var undef @binding_point(0, 0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^)"));
}
TEST_F(IR_ValidatorTest, ResourceBinding_WithCapabilityPasses) {
b.Append(mod.root_block, [&] {
auto* var = b.Var("m", AddressSpace::kHandle, ty.Get<core::type::ResourceBinding>());
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllowResourceBinding});
ASSERT_EQ(res, Success) << res.Failure();
}
using Type_MultisampledTextureTypeAndDimension =
IRTestParamHelper<std::tuple<std::tuple<
/* type_allowed */ bool,
/* type_builder */ TypeBuilderFn>,
std::tuple<
/* dim_allowed */ bool,
/* dim */ core::type::TextureDimension>>>;
TEST_P(Type_MultisampledTextureTypeAndDimension, Test) {
auto type_params = std::get<0>(GetParam());
bool type_allowed = std::get<0>(type_params);
auto* type = std::get<1>(type_params)(ty);
auto dim_params = std::get<1>(GetParam());
bool dim_allowed = std::get<0>(dim_params);
auto dim = std::get<1>(dim_params);
bool allowed = type_allowed && dim_allowed;
b.Append(mod.root_block, [&] {
auto* var = b.Var("ms", AddressSpace::kHandle, ty.multisampled_texture(dim, type));
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod);
if (allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
if (!type_allowed) {
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(":2:3 error: var: invalid multisampled texture sample type: '" +
type->FriendlyName()))
<< res.Failure();
} else {
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(":2:3 error: var: invalid multisampled texture dimension: '" +
std::string(ToString(dim))))
<< res.Failure();
}
}
}
INSTANTIATE_TEST_SUITE_P(
IR_ValidatorTest,
Type_MultisampledTextureTypeAndDimension,
testing::Combine(testing::Values(std::make_tuple(true, TypeBuilder<f32>),
std::make_tuple(true, TypeBuilder<i32>),
std::make_tuple(true, TypeBuilder<u32>),
std::make_tuple(false, TypeBuilder<f16>),
std::make_tuple(false, TypeBuilder<core::type::Bool>),
std::make_tuple(false, TypeBuilder<core::type::Void>)),
testing::Values(std::make_tuple(false, core::type::TextureDimension::k1d),
std::make_tuple(true, core::type::TextureDimension::k2d),
std::make_tuple(false, core::type::TextureDimension::k2dArray),
std::make_tuple(false, core::type::TextureDimension::k3d),
std::make_tuple(false, core::type::TextureDimension::kCube),
std::make_tuple(false,
core::type::TextureDimension::kCubeArray),
std::make_tuple(false, core::type::TextureDimension::kNone))));
using Type_StorageTextureDimension = IRTestParamHelper<std::tuple<
/* allowed */ bool,
/* dim */ core::type::TextureDimension>>;
TEST_P(Type_StorageTextureDimension, Test) {
bool allowed = std::get<0>(GetParam());
auto dim = std::get<1>(GetParam());
auto* v =
b.Var("v", AddressSpace::kHandle,
ty.storage_texture(dim, core::TexelFormat::kRgba32Float, core::Access::kReadWrite),
core::Access::kRead);
v->SetBindingPoint(0, 0);
mod.root_block->Append(v);
if (allowed) {
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
} else {
auto res = ir::Validate(mod);
ASSERT_NE(res, Success) << res.Failure();
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
dim != type::TextureDimension::kNone
? R"(:2:3 error: var: dimension ')" + std::string(ToString(dim)) +
R"(' for storage textures does not in WGSL yet)"
: R"(:2:3 error: var: invalid texture dimension 'none')"))
<< res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(
IR_ValidatorTest,
Type_StorageTextureDimension,
testing::Values(std::make_tuple(true, core::type::TextureDimension::k2d),
std::make_tuple(false, core::type::TextureDimension::kCube),
std::make_tuple(false, core::type::TextureDimension::kCubeArray),
std::make_tuple(false, core::type::TextureDimension::kNone)));
using Type_InputAttachmentComponentType = TypeTest;
TEST_P(Type_InputAttachmentComponentType, Test) {
bool allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
b.Append(mod.root_block, [&] {
auto* var = b.Var("m", AddressSpace::kHandle, ty.input_attachment(type));
var->SetBindingPoint(0, 0);
});
auto res = ir::Validate(mod);
if (allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(":2:3 error: var: invalid input attachment component type: '" +
type->FriendlyName()))
<< res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
Type_InputAttachmentComponentType,
testing::Values(std::make_tuple(true, TypeBuilder<f32>),
std::make_tuple(true, TypeBuilder<i32>),
std::make_tuple(true, TypeBuilder<u32>),
std::make_tuple(false, TypeBuilder<f16>),
std::make_tuple(false, TypeBuilder<core::type::Bool>),
std::make_tuple(false, TypeBuilder<core::type::Void>)));
using IR_ValidatorRefTypeTest = IRTestParamHelper<std::tuple</* holds_ref */ bool,
/* refs_allowed */ bool,
/* type_builder */ TypeBuilderFn>>;
TEST_P(IR_ValidatorRefTypeTest, Var) {
bool holds_ref = std::get<0>(GetParam());
bool refs_allowed = std::get<1>(GetParam());
auto* type = std::get<2>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
if (auto* view = type->As<core::type::MemoryView>()) {
b.Var(view);
} else {
b.Var(ty.ptr<function>(type));
}
b.Return(fn);
});
Capabilities caps;
if (refs_allowed) {
caps.Add(Capability::kAllowRefTypes);
}
auto res = ir::Validate(mod, caps);
if (!holds_ref || refs_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("3:5 error: var: reference types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_ValidatorRefTypeTest, FnParam) {
bool holds_ref = std::get<0>(GetParam());
bool refs_allowed = std::get<1>(GetParam());
auto* type = std::get<2>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { b.Return(fn); });
Capabilities caps;
if (refs_allowed) {
caps.Add(Capability::kAllowRefTypes);
}
auto res = ir::Validate(mod, caps);
if (!holds_ref) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("reference types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_ValidatorRefTypeTest, FnRet) {
bool holds_ref = std::get<0>(GetParam());
bool refs_allowed = std::get<1>(GetParam());
auto* type = std::get<2>(GetParam())(ty);
auto* fn = b.Function("my_func", type);
b.Append(fn->Block(), [&] { b.Unreachable(); });
Capabilities caps;
if (refs_allowed) {
caps.Add(Capability::kAllowRefTypes);
}
auto res = ir::Validate(mod, caps);
if (!holds_ref) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("reference types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_ValidatorRefTypeTest, BlockParam) {
bool holds_ref = std::get<0>(GetParam());
bool refs_allowed = std::get<1>(GetParam());
auto* type = std::get<2>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
auto* loop = b.Loop();
loop->Continuing()->SetParams({b.BlockParam(type)});
b.Append(loop->Body(), [&] { //
b.Continue(loop, nullptr);
});
b.Append(loop->Continuing(), [&] { //
b.NextIteration(loop);
});
b.Unreachable();
});
Capabilities caps;
if (refs_allowed) {
caps.Add(Capability::kAllowRefTypes);
}
auto res = ir::Validate(mod, caps);
if (!holds_ref) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("reference types are not permitted"))
<< res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(NonRefTypes,
IR_ValidatorRefTypeTest,
testing::Combine(/* holds_ref */ testing::Values(false),
/* refs_allowed */ testing::Values(false, true),
/* type_builder */
testing::Values(TypeBuilder<i32>,
TypeBuilder<bool>,
TypeBuilder<vec4<f32>>,
TypeBuilder<array<f32, 3>>)));
INSTANTIATE_TEST_SUITE_P(RefTypes,
IR_ValidatorRefTypeTest,
testing::Combine(/* holds_ref */ testing::Values(true),
/* refs_allowed */ testing::Values(false, true),
/* type_builder */
testing::Values(RefTypeBuilder<i32>,
RefTypeBuilder<bool>,
RefTypeBuilder<vec4<f32>>)));
TEST_F(IR_ValidatorTest, PointerToPointer) {
auto* type = ty.ptr<function, ptr<function, i32>>();
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { //
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("nested pointer types are not permitted"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, PointerToVoid) {
auto* type = ty.ptr(AddressSpace::kFunction, ty.void_());
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { //
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("pointers to void are not permitted"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, ReferenceToReference) {
auto* type = ty.ref<function>(ty.ref<function, i32>());
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] { //
b.Var(type);
b.Return(fn);
});
Capabilities caps;
caps.Add(Capability::kAllowRefTypes);
auto res = ir::Validate(mod, caps);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("nested reference types are not permitted"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, ReferenceToVoid) {
auto* type = ty.ref(AddressSpace::kFunction, ty.void_());
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] { //
b.Var(type);
b.Return(fn);
});
Capabilities caps;
caps.Add(Capability::kAllowRefTypes);
auto res = ir::Validate(mod, caps);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("references to void are not permitted"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, PointerInStructure_WithoutCapability) {
auto* str_ty =
ty.Struct(mod.symbols.New("S"), {
{mod.symbols.New("a"), ty.ptr<private_, i32>()},
});
mod.root_block->Append(b.Var("my_struct", private_, str_ty));
auto* fn = b.Function("F", ty.void_());
b.Append(fn->Block(), [&] { b.Return(fn); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr("nested pointer types are not permitted"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, PointerInStructure_WithCapability) {
auto* str_ty =
ty.Struct(mod.symbols.New("S"), {
{mod.symbols.New("a"), ty.ptr<private_, i32>()},
});
auto* fn = b.Function("F", ty.void_());
auto* param = b.FunctionParam("param", str_ty);
fn->SetParams({param});
b.Append(fn->Block(), [&] { b.Return(fn); });
auto res = ir::Validate(mod, Capabilities{Capability::kAllowPointersAndHandlesInStructures});
EXPECT_EQ(res, Success) << res.Failure();
}
using IR_Validator8BitIntTypeTest = IRTestParamHelper<std::tuple<
/* int8_allowed */ bool,
/* type_builder */ TypeBuilderFn>>;
TEST_P(IR_Validator8BitIntTypeTest, Var) {
bool int8_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
b.Var(ty.ptr<function>(type));
b.Return(fn);
});
Capabilities caps;
if (int8_allowed) {
caps.Add(Capability::kAllow8BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int8_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("3:5 error: var: 8-bit integer types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_Validator8BitIntTypeTest, FnParam) {
bool int8_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { b.Return(fn); });
Capabilities caps;
if (int8_allowed) {
caps.Add(Capability::kAllow8BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int8_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("8-bit integer types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_Validator8BitIntTypeTest, FnRet) {
bool int8_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", type);
b.Append(fn->Block(), [&] { b.Unreachable(); });
Capabilities caps;
if (int8_allowed) {
caps.Add(Capability::kAllow8BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int8_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("8-bit integer types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_Validator8BitIntTypeTest, BlockParam) {
bool int8_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
auto* loop = b.Loop();
loop->Continuing()->SetParams({b.BlockParam(type)});
b.Append(loop->Body(), [&] { //
b.Continue(loop, nullptr);
});
b.Append(loop->Continuing(), [&] { //
b.NextIteration(loop);
});
b.Unreachable();
});
Capabilities caps;
if (int8_allowed) {
caps.Add(Capability::kAllow8BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int8_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("8-bit integer types are not permitted"))
<< res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(Int8Types,
IR_Validator8BitIntTypeTest,
testing::Combine(
/* int8_allowed */ testing::Values(false, true),
/* type_builder */
testing::Values(TypeBuilder<i8>,
TypeBuilder<u8>,
TypeBuilder<vec4<i8>>,
TypeBuilder<array<u8, 4>>)));
TEST_F(IR_ValidatorTest, Int8Type_InstructionOperand_NotAllowed) {
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
b.Let("l", u8(1));
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: let: 8-bit integer types are not permitted
%l:u8 = let 1u8
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Int8Type_InstructionOperand_Allowed) {
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
b.Let("l", u8(1));
b.Return(fn);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllow8BitIntegers});
ASSERT_EQ(res, Success) << res.Failure();
}
using IR_Validator64BitIntTypeTest = IRTestParamHelper<std::tuple<
/* int64_allowed */ bool,
/* type_builder */ TypeBuilderFn>>;
TEST_P(IR_Validator64BitIntTypeTest, Var) {
bool int64_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
b.Var(ty.ptr<function>(type));
b.Return(fn);
});
Capabilities caps;
if (int64_allowed) {
caps.Add(Capability::kAllow64BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int64_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("3:5 error: var: 64-bit integer types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_Validator64BitIntTypeTest, FnParam) {
bool int64_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
fn->SetParams(Vector{b.FunctionParam(type)});
b.Append(fn->Block(), [&] { b.Return(fn); });
Capabilities caps;
if (int64_allowed) {
caps.Add(Capability::kAllow64BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int64_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("64-bit integer types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_Validator64BitIntTypeTest, FnRet) {
bool int64_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", type);
b.Append(fn->Block(), [&] { b.Unreachable(); });
Capabilities caps;
if (int64_allowed) {
caps.Add(Capability::kAllow64BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int64_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("64-bit integer types are not permitted"))
<< res.Failure();
}
}
TEST_P(IR_Validator64BitIntTypeTest, BlockParam) {
bool int64_allowed = std::get<0>(GetParam());
auto* type = std::get<1>(GetParam())(ty);
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
auto* loop = b.Loop();
loop->Continuing()->SetParams({b.BlockParam(type)});
b.Append(loop->Body(), [&] { //
b.Continue(loop, nullptr);
});
b.Append(loop->Continuing(), [&] { //
b.NextIteration(loop);
});
b.Unreachable();
});
Capabilities caps;
if (int64_allowed) {
caps.Add(Capability::kAllow64BitIntegers);
}
auto res = ir::Validate(mod, caps);
if (int64_allowed) {
ASSERT_EQ(res, Success) << res.Failure();
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr("64-bit integer types are not permitted"))
<< res.Failure();
}
}
INSTANTIATE_TEST_SUITE_P(Int64Types,
IR_Validator64BitIntTypeTest,
testing::Combine(
/* int64_allowed */ testing::Values(false, true),
/* type_builder */
testing::Values(TypeBuilder<u64>, //
TypeBuilder<vec4<u64>>,
TypeBuilder<array<u64, 4>>)));
TEST_F(IR_ValidatorTest, Int64Type_InstructionOperand_NotAllowed) {
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
b.Let("l", u64(1));
b.Return(fn);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: let: 64-bit integer types are not permitted
%l:u64 = let 1u64
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Int64Type_InstructionOperand_Allowed) {
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
b.Let("l", u64(1));
b.Return(fn);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllow64BitIntegers});
ASSERT_EQ(res, Success) << res.Failure();
}
using AddressSpace_AccessMode = IRTestParamHelper<std::tuple<
/* address */ AddressSpace,
/* access mode */ core::Access>>;
TEST_P(AddressSpace_AccessMode, Test) {
auto aspace = std::get<0>(GetParam());
auto access = std::get<1>(GetParam());
if (aspace == AddressSpace::kFunction) {
auto* fn = b.Function("my_func", ty.void_());
b.Append(fn->Block(), [&] {
b.Var("v", aspace, ty.u32(), access);
b.Return(fn);
});
} else {
const core::type::Type* sampler_ty = ty.sampler();
const core::type::Type* u32_ty = ty.u32();
auto* type = aspace == AddressSpace::kHandle ? sampler_ty : u32_ty;
auto* v = b.Var("v", aspace, type, access);
if (aspace != AddressSpace::kPrivate && aspace != AddressSpace::kWorkgroup) {
v->SetBindingPoint(0, 0);
}
mod.root_block->Append(v);
}
const char* expected_error = nullptr;
switch (access) {
case core::Access::kWrite:
if (aspace == AddressSpace::kUniform || aspace == AddressSpace::kHandle) {
expected_error = "uniform and handle pointers must be read access";
} else if (aspace == AddressSpace::kWorkgroup) {
expected_error = "workgroup pointers must be read_write access";
} else if (aspace == AddressSpace::kStorage) {
expected_error =
"vars in the 'storage' address space must have access 'read' or 'read-write'";
}
break;
case core::Access::kReadWrite:
if (aspace == AddressSpace::kUniform || aspace == AddressSpace::kHandle) {
expected_error = "uniform and handle pointers must be read access";
}
break;
case core::Access::kRead:
if (aspace == AddressSpace::kWorkgroup) {
expected_error = "workgroup pointers must be read_write access";
}
break;
default:
break;
}
auto res = ir::Validate(mod);
if (expected_error == nullptr) {
ASSERT_EQ(res, Success);
} else {
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(expected_error));
}
}
INSTANTIATE_TEST_SUITE_P(IR_ValidatorTest,
AddressSpace_AccessMode,
testing::Combine(testing::Values(AddressSpace::kFunction,
AddressSpace::kPrivate,
AddressSpace::kWorkgroup,
AddressSpace::kUniform,
AddressSpace::kStorage,
AddressSpace::kHandle),
testing::Values(core::Access::kRead,
core::Access::kWrite,
core::Access::kReadWrite)));
} // namespace tint::core::ir