blob: 901b89cae8b3b892597be8769a79a73bce86956c [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 "gtest/gtest.h"
#include "src/tint/lang/core/enums.h"
#include "src/tint/lang/core/ir/builder.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/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/storage_texture.h"
namespace tint::core::ir {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
TEST_F(IR_ValidatorTest, Access_NoOperands) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.vec3<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
auto* access = b.Access(ty.f32(), obj, 0_i);
access->ClearOperands();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:14 error: access: expected at least 2 operands, got 0
%3:f32 = access
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_NoIndices) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.vec3<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:14 error: access: expected at least 2 operands, got 1
%3:f32 = access %2
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_NoResults) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.vec3<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
auto* access = b.Access(ty.f32(), obj, 0_i);
access->ClearResults();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:13 error: access: expected exactly 1 results, got 0
undef = access %2, 0i
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_NullObject) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Access(ty.f32(), nullptr, 0_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:21 error: access: operand is undefined
%2:f32 = access undef, 0u
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_NullIndex) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.vec3<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, nullptr);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:25 error: access: operand is undefined
%3:f32 = access %2, undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_NegativeIndex) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.vec3<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, -1_i);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:25 error: access: constant index must be positive, got -1
%3:f32 = access %2, -1i
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_OOB_Index_Value) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.mat3x2<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, 1_u, 3_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:29 error: access: index out of bounds for type 'vec2<f32>'
%3:f32 = access %2, 1u, 3u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_OOB_Index_Ptr) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, array<array<f32, 2>, 3>>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.ptr<private_, f32>(), obj, 1_u, 3_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:55 error: access: index out of bounds for type 'ptr<private, array<f32, 2>, read_write>'
%3:ptr<private, f32, read_write> = access %2, 1u, 3u
^^
:2:3 note: in block
$B1: {
^^^
:3:55 note: acceptable range: [0..1]
%3:ptr<private, f32, read_write> = access %2, 1u, 3u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_StaticallyUnindexableType_Value) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.f32());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:25 error: access: type 'f32' cannot be indexed
%3:f32 = access %2, 1u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_StaticallyUnindexableType_Ptr) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.ptr<private_, f32>(), obj, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:51 error: access: type 'ptr<private, f32, read_write>' cannot be indexed
%3:ptr<private, f32, read_write> = access %2, 1u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_DynamicallyUnindexableType_Value) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.i32()},
});
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(str_ty);
auto* idx = b.FunctionParam(ty.i32());
f->SetParams({obj, idx});
b.Append(f->Block(), [&] {
b.Access(ty.i32(), obj, idx);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:8:25 error: access: type 'MyStruct' cannot be dynamically indexed
%4:i32 = access %2, %3
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_DynamicallyUnindexableType_Ptr) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.i32()},
});
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, read_write>(str_ty));
auto* idx = b.FunctionParam(ty.i32());
f->SetParams({obj, idx});
b.Append(f->Block(), [&] {
b.Access(ty.i32(), obj, idx);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:8:25 error: access: type 'ptr<private, MyStruct, read_write>' cannot be dynamically indexed
%4:i32 = access %2, %3
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Value_Value) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.mat3x2<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.i32(), obj, 1_u, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:14 error: access: result of access chain is type 'f32' but instruction type is 'i32'
%3:i32 = access %2, 1u, 1u
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Ptr_Ptr) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, array<array<f32, 2>, 3>>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.ptr<private_, i32>(), obj, 1_u, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:40 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'ptr<private, i32, read_write>'
%3:ptr<private, i32, read_write> = access %2, 1u, 1u
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_Incorrect_Type_Ptr_Value) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, array<array<f32, 2>, 3>>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, 1_u, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:14 error: access: result of access chain is type 'ptr<private, f32, read_write>' but instruction type is 'f32'
%3:f32 = access %2, 1u, 1u
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_IndexVectorPtr) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, vec3<f32>>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:25 error: access: cannot obtain address of vector element
%3:f32 = access %2, 1u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_WithCapability) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, vec3<f32>>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.ptr<private_, f32>(), obj, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllowVectorElementPointer});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_ViaMatrixPtr) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2<f32>>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, 1_u, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:29 error: access: cannot obtain address of vector element
%3:f32 = access %2, 1u, 1u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_IndexVectorPtr_ViaMatrixPtr_WithCapability) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2<f32>>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.ptr<private_, f32>(), obj, 1_u, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllowVectorElementPointer});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_Incorrect_Ptr_AddressSpace) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<storage, array<f32, 2>, read>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.ptr<uniform, f32, read>(), obj, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:34 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<uniform, f32, read>'
%3:ptr<uniform, f32, read> = access %2, 1u
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_Incorrect_Ptr_Access) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.ptr<storage, array<f32, 2>, read>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.ptr<storage, f32, read_write>(), obj, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:40 error: access: result of access chain is type 'ptr<storage, f32, read>' but instruction type is 'ptr<storage, f32, read_write>'
%3:ptr<storage, f32, read_write> = access %2, 1u
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_IndexVector) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.vec3<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_IndexVector_ViaMatrix) {
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam(ty.mat3x2<f32>());
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ty.f32(), obj, 1_u, 1_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Access_ExtractPointerFromStruct) {
auto* ptr = ty.ptr<private_, i32>();
Vector<core::type::Manager::StructMemberDesc, 1> members{
core::type::Manager::StructMemberDesc{mod.symbols.New("a"), ptr},
};
auto* str = ty.Struct(mod.symbols.New("MyStruct"), std::move(members));
auto* f = b.Function("my_func", ty.void_());
auto* obj = b.FunctionParam("obj", str);
f->SetParams({obj});
b.Append(f->Block(), [&] {
b.Access(ptr, obj, 0_u);
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllowPointersAndHandlesInStructures});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Load_NullFrom) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.i32()), nullptr));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:19 error: load: operand is undefined
%2:i32 = load undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Load_SourceNotMemoryView) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* let = b.Let("l", 1_i);
b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.f32()), let->Result()));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:4:19 error: load: load source operand 'i32' is not a memory view
%3:f32 = load %l
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Load_TypeMismatch) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32>());
b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.f32()), var->Result()));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:4:19 error: load: result type 'f32' does not match source store type 'i32'
%3:f32 = load %2
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Load_MissingResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32>());
auto* load = mod.CreateInstruction<ir::Load>(nullptr, var->Result());
load->ClearResults();
b.Append(load);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:4:13 error: load: expected exactly 1 results, got 0
undef = load %2
^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Load_NonReadableSource) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32, core::Access::kWrite>());
b.Append(mod.CreateInstruction<ir::Load>(b.InstructionResult(ty.i32()), var->Result()));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:19 error: load: load source operand has a non-readable access type, 'write'
%3:i32 = load %2
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Load_RuntimeSizedArray) {
auto* a = b.Var("a", ty.ptr<storage, array<u32>, read_write>());
a->SetBindingPoint(0, 0);
mod.root_block->Append(a);
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Load(a);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
R"(:7:21 error: load: cannot load a runtime-sized array
%3:array<u32> = load %a
^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_NullTo) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Append(mod.CreateInstruction<ir::Store>(nullptr, b.Constant(42_i)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:11 error: store: operand is undefined
store undef, 42i
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_NullFrom) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32>());
b.Append(mod.CreateInstruction<ir::Store>(var->Result(), nullptr));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:4:15 error: store: operand is undefined
store %2, undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_NonEmptyResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32>());
auto* store = mod.CreateInstruction<ir::Store>(var->Result(), b.Constant(42_i));
store->SetResult(b.InstructionResult(ty.i32()));
b.Append(store);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:4:5 error: store: expected exactly 0 results, got 1
store %2, 42i
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_TargetNotMemoryView) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* let = b.Let("l", 1_i);
b.Append(mod.CreateInstruction<ir::Store>(let->Result(), b.Constant(42_u)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:4:11 error: store: store target operand 'i32' is not a memory view
store %l, 42u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_TypeMismatch) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32>());
b.Append(mod.CreateInstruction<ir::Store>(var->Result(), b.Constant(42_u)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:4:15 error: store: value type 'u32' does not match store type 'i32'
store %2, 42u
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_NoStoreType) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* result = b.InstructionResult(ty.u32());
result->SetType(nullptr);
b.Append(mod.CreateInstruction<ir::Store>(result, b.Constant(42_u)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:11 error: store: operand type is undefined
store %2, 42u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_NoValueType) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32>());
auto* val = b.Construct(ty.u32(), 42_u);
val->Result()->SetType(nullptr);
b.Append(mod.CreateInstruction<ir::Store>(var->Result(), val->Result()));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:5:15 error: store: operand type is undefined
store %2, %3
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_NonWriteableTarget) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, i32, core::Access::kRead>());
b.Append(mod.CreateInstruction<ir::Store>(var->Result(), b.Constant(42_i)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:11 error: store: store target operand has a non-writeable access type, 'read'
store %2, 42i
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Store_NonConstructible) {
auto* a = b.Var("a", ty.ptr<storage, array<u32>, read_write>());
a->SetBindingPoint(0, 0);
mod.root_block->Append(a);
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
// Note: The load is invalid too, but there's no way to produce a non-constructible value
// that will not hit another validation rule before the constructible check.
b.Store(a, b.Load(a));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:8:5 error: store: store type 'array<u32>' is not constructible
store %a, %3
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_NullResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
b.Append(
mod.CreateInstruction<ir::LoadVectorElement>(nullptr, var->Result(), b.Constant(1_i)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:4:5 error: load_vector_element: result is undefined
undef = load_vector_element %2, 1i
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_NullFrom) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Append(mod.CreateInstruction<ir::LoadVectorElement>(b.InstructionResult(ty.f32()),
nullptr, b.Constant(1_i)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:34 error: load_vector_element: operand is undefined
%2:f32 = load_vector_element undef, 1i
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_NullIndex) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
b.Append(mod.CreateInstruction<ir::LoadVectorElement>(b.InstructionResult(ty.f32()),
var->Result(), nullptr));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:4:38 error: load_vector_element: operand is undefined
%3:f32 = load_vector_element %2, undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_MissingResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
auto* load = b.LoadVectorElement(var, b.Constant(1_i));
load->ClearResults();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:4:13 error: load_vector_element: expected exactly 1 results, got 0
undef = load_vector_element %2, 1i
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_MissingOperands) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
auto* load = b.LoadVectorElement(var, b.Constant(1_i));
load->ClearOperands();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:4:14 error: load_vector_element: expected exactly 2 operands, got 0
%3:f32 = load_vector_element
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_InvalidIndexType) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
b.LoadVectorElement(var->Result(), 1_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:38 error: load_vector_element: load vector element index must be an integer scalar
%3:f32 = load_vector_element %2, 1.0f
^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_InvalidFromType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam(ty.mat2x3<f32>());
f->SetParams({p});
b.Append(f->Block(), [&] {
auto* res = b.InstructionResult(ty.f32());
auto* lve = mod.CreateInstruction<ir::LoadVectorElement>(res, p, b.Constant(1_u));
b.Append(lve);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:34 error: load_vector_element: operand 'mat2x3<f32>' must be a pointer to a vector
%3:f32 = load_vector_element %2, 1u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, LoadVectorElement_ConstantIndexOutOfRange) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec4<f32>>());
b.LoadVectorElement(var->Result(), 7_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:38 error: load_vector_element: load vector element index must be in range [0, 3]
%3:f32 = load_vector_element %2, 7u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_NullTo) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Append(mod.CreateInstruction<ir::StoreVectorElement>(nullptr, b.Constant(1_i),
b.Constant(2_f)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:26 error: store_vector_element: operand is undefined
store_vector_element undef, 1i, 2.0f
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_NullIndex) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
b.Append(
mod.CreateInstruction<ir::StoreVectorElement>(var->Result(), nullptr, b.Constant(2_f)));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:4:30 error: store_vector_element: operand is undefined
store_vector_element %2, undef, 2.0f
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_NullValue) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
b.Append(
mod.CreateInstruction<ir::StoreVectorElement>(var->Result(), b.Constant(1_i), nullptr));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:4:34 error: store_vector_element: operand is undefined
store_vector_element %2, 1i, undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_MissingOperands) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
auto* store = b.StoreVectorElement(var, b.Constant(1_i), b.Constant(2_f));
store->ClearOperands();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:4:5 error: store_vector_element: expected exactly 3 operands, got 0
store_vector_element
^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_UnexpectedResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
auto* store = b.StoreVectorElement(var, b.Constant(1_i), b.Constant(2_f));
store->SetResult(b.InstructionResult(ty.f32()));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:4:5 error: store_vector_element: expected exactly 0 results, got 1
store_vector_element %2, 1i, 2.0f
^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_InvalidIndexType) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec3<f32>>());
b.StoreVectorElement(var->Result(), 1_f, 1_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:30 error: store_vector_element: store vector element index must be an integer scalar
store_vector_element %2, 1.0f, 1.0f
^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_InvalidFromType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam(ty.mat2x3<f32>());
f->SetParams({p});
b.Append(f->Block(), [&] {
auto* sve =
mod.CreateInstruction<ir::StoreVectorElement>(p, b.Constant(1_u), b.Constant(2_u));
b.Append(sve);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:26 error: store_vector_element: operand 'mat2x3<f32>' must be a pointer to a vector
store_vector_element %2, 1u, 2u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, StoreVectorElement_ConstantIndexOutOfRange) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr<function, vec2<f32>>());
b.StoreVectorElement(var->Result(), 7_u, 1_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:30 error: store_vector_element: store vector element index must be in range [0, 1]
store_vector_element %2, 7u, 1.0f
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_MissingValue) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
auto* swizzle = b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
swizzle->ClearOperands();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:5:20 error: swizzle: expected exactly 1 operands, got 0
%4:vec4<f32> = swizzle undef, wzyx
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_NullValue) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
auto* swizzle = b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
swizzle->SetOperand(0, nullptr);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(error: swizzle: operand is undefined
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_MissingResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
auto* swizzle = b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
swizzle->ClearResults();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:5:13 error: swizzle: expected exactly 1 results, got 0
undef = swizzle %3, wzyx
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_NullResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
auto* swizzle = b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
swizzle->SetResult(nullptr);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:5:5 error: swizzle: result is undefined
undef = swizzle %3, wzyx
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_NoIndices) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
auto* swizzle = b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
auto indices = Vector<uint32_t, 0>();
swizzle->SetIndices(std::move(indices));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:5:20 error: swizzle: expected at least 1 indices
%4:vec4<f32> = swizzle %3,
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_TooManyIndices) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
auto* swizzle = b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
auto indices = Vector<uint32_t, 5>{1, 1, 1, 1, 1};
swizzle->SetIndices(std::move(indices));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:5:20 error: swizzle: expected at most 4 indices
%4:vec4<f32> = swizzle %3, yyyyy
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_InvalidIndices) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
auto* swizzle = b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
auto indices = Vector<uint32_t, 4>{4, 3, 2, 1};
swizzle->SetIndices(std::move(indices));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:5:20 error: swizzle: invalid index value
%4:vec4<f32> = swizzle %3, wzy
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_NotVector) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.f32()));
b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2, 1, 0});
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:5:20 error: swizzle: object of swizzle, %3, is not a vector, 'f32'
%4:vec4<f32> = swizzle %3, wzyx
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_TooSmallResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
b.Swizzle(ty.vec2<f32>(), b.Load(var), {3, 2, 1, 0});
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:20 error: swizzle: result type 'vec2<f32>' does not match expected type, 'vec4<f32>'
%4:vec2<f32> = swizzle %3, wzyx
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_TooLargeResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 2});
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:20 error: swizzle: result type 'vec4<f32>' does not match expected type, 'vec2<f32>'
%4:vec4<f32> = swizzle %3, wz
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_WrongTypeResult) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec4<f32>()));
b.Swizzle(ty.vec2<u32>(), b.Load(var), {3, 2});
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(5:20 error: swizzle: result type 'vec2<u32>' does not match expected type, 'vec2<f32>'
%4:vec2<u32> = swizzle %3, wz
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Swizzle_OOBIndex) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* var = b.Var(ty.ptr(function, ty.vec2<f32>()));
b.Swizzle(ty.vec4<f32>(), b.Load(var), {3, 1, 1, 0});
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:5:20 error: swizzle: invalid index value
%4:vec4<f32> = swizzle %3, wyyx
^^^^^^^
)")) << res.Failure();
}
} // namespace tint::core::ir