blob: 7ef5066292b52845b9ec3bb8d5f4ad5bc117a1e5 [file] [edit]
// Copyright 2023 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 "gtest/gtest.h"
#include "src/tint/lang/core/ir/builder.h"
#include "src/tint/lang/core/ir/function_param.h"
#include "src/tint/lang/core/ir/type/array_count.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/reference.h"
#include "src/tint/lang/core/type/sampled_texture.h"
#include "src/tint/lang/core/type/storage_texture.h"
#include "src/tint/lang/core/type/struct.h"
#include "src/tint/utils/text/string.h"
namespace tint::core::ir {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
Function* IR_ValidatorTest::ComputeEntryPoint(const std::string& name) {
return b.ComputeFunction(name);
}
Function* IR_ValidatorTest::FragmentEntryPoint(const std::string& name) {
return b.Function(name, ty.void_(), Function::PipelineStage::kFragment);
}
Function* IR_ValidatorTest::VertexEntryPoint(const std::string& name) {
auto* f = b.Function(name, ty.vec4<f32>(), Function::PipelineStage::kVertex);
f->SetReturnBuiltin(BuiltinValue::kPosition);
return f;
}
void IR_ValidatorTest::AddBuiltinParam(Function* func,
const std::string& name,
BuiltinValue builtin,
const core::type::Type* type) {
auto* p = b.FunctionParam(name, type);
p->SetBuiltin(builtin);
func->AppendParam(p);
}
void IR_ValidatorTest::AddReturn(Function* func,
const std::string& name,
const core::type::Type* type,
const IOAttributes& attr) {
if (func->ReturnType()->Is<core::type::Struct>()) {
TINT_ICE() << "AddReturn does not support adding to structured returns";
}
if (func->ReturnType() == ty.void_()) {
func->SetReturnAttributes(attr);
func->SetReturnType(type);
return;
}
std::string old_name =
func->ReturnAttributes().builtin == BuiltinValue::kPosition ? "pos" : "old_ret";
auto* str_ty =
ty.Struct(mod.symbols.New("OutputStruct"),
{
{mod.symbols.New(old_name), func->ReturnType(), func->ReturnAttributes()},
{mod.symbols.New(name), type, attr},
});
func->SetReturnAttributes({});
func->SetReturnType(str_ty);
}
void IR_ValidatorTest::AddBuiltinReturn(Function* func,
const std::string& name,
BuiltinValue builtin,
const core::type::Type* type) {
IOAttributes attr;
attr.builtin = builtin;
AddReturn(func, name, type, attr);
}
TEST_F(IR_ValidatorTest, RootBlock_Var) {
mod.root_block->Append(b.Var(ty.ptr<private_, i32>()));
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, RootBlock_NonVar) {
auto* l = b.Loop();
l->Body()->Append(b.Continue(l));
mod.root_block->Append(l);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:2:3 error: loop: root block: invalid instruction: tint::core::ir::Loop
loop [b: $B2] { # loop_1
^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, RootBlock_Let) {
mod.root_block->Append(b.Let("a", 1_f));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:2:12 error: let: root block: invalid instruction: tint::core::ir::Let
%a:f32 = let 1.0f
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, RootBlock_LetWithAllowModuleScopeLets) {
mod.root_block->Append(b.Let("a", 1_f));
auto res = ir::Validate(mod, Capabilities{Capability::kAllowModuleScopeLets});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, RootBlock_Construct) {
mod.root_block->Append(b.Construct(ty.vec2<f32>(), 1_f, 2_f));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:2:18 error: construct: root block: invalid instruction: tint::core::ir::Construct
%1:vec2<f32> = construct 1.0f, 2.0f
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, RootBlock_ConstructWithAllowModuleScopeLets) {
mod.root_block->Append(b.Construct(ty.vec2<f32>(), 1_f, 2_f));
auto res = ir::Validate(mod, Capabilities{Capability::kAllowModuleScopeLets});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, RootBlock_VarBlockMismatch) {
auto* var = b.Var(ty.ptr<private_, i32>());
mod.root_block->Append(var);
auto* f = b.Function("f", ty.void_());
f->Block()->Append(b.Return(f));
var->SetBlock(f->Block());
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:2:38 error: var: instruction in root block does not have root block as parent
%1:ptr<private, i32, read_write> = var undef
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Scalar_WrongArgType) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct<u32>(42_i);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:24 error: construct: scalar construct argument type 'i32' does not match result type 'u32'
%2:u32 = construct 42i
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Scalar_TooManyArguments) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct<u32>(42_u, 10_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:14 error: construct: scalar construct must not have more than one argument
%2:u32 = construct 42u, 10u
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_SubgroupMatrix_WrongArgType) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.subgroup_matrix_left(ty.f32(), 2, 3), f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:42 error: construct: subgroup matrix construct argument type '<function>' does not match matrix type 'f32'
%2:subgroup_matrix_left<f32, 2, 3> = construct %f
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_SubgroupMatrix_TooManyArguments) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.subgroup_matrix_left(ty.f32(), 2, 3), 42_f, 43_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:42 error: construct: subgroup matrix construct must not have more than 1 argument
%2:subgroup_matrix_left<f32, 2, 3> = construct 42.0f, 43.0f
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Array_WrongArgType) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct<array<u32, 4>>(1_u, 2_u, 3_i, 4_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:42 error: construct: type 'i32' of argument 2 does not match expected type 'u32'
%2:array<u32, 4> = construct 1u, 2u, 3i, 4u
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Matrix_NoArgs) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.mat2x2<f32>());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Matrix_Scalar) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.mat2x2<f32>(), 1_f, 2_f, 3_f, 4_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Matrix_ColumnVectors) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* v1 = b.Composite(ty.vec2<f32>(), 1_f, 2_f);
auto* v2 = b.Composite(ty.vec2<f32>(), 3_f, 4_f);
b.Construct(ty.mat2x2<f32>(), v1, v2);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Matrix_MixedScalarVector) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.mat2x2<f32>(), 1_f, b.Composite(ty.vec2<f32>(), 2_f, 3_f));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr("error: construct: no matching overload for mat2x2<f32> constructor"));
}
TEST_F(IR_ValidatorTest, Construct_Matrix_Scalar_WrongType) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.mat2x2<f32>(), 1_f, 2_f, 3_h, 4_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr("error: construct: no matching overload for mat2x2<f32> constructor"));
}
TEST_F(IR_ValidatorTest, Construct_Struct_ZeroValue) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Struct_ValidArgs) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty, 1_i, 2_u);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Struct_UnusedArgs) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty, 1_i, b.Unused());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Struct_NotEnoughArgs) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty, 1_i);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:8:19 error: construct: structure has 2 members, but construct provides 1 arguments
%2:MyStruct = construct 1i
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Struct_TooManyArgs) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty, 1_i, 2_u, 3_i);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:8:19 error: construct: structure has 2 members, but construct provides 3 arguments
%2:MyStruct = construct 1i, 2u, 3i
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Struct_WrongArgType) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty, 1_i, 2_i);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:8:33 error: construct: type 'i32' of argument 1 does not match expected type 'u32'
%2:MyStruct = construct 1i, 2i
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_NullArg) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty, 1_i, nullptr);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:8:33 error: construct: operand is undefined
%2:MyStruct = construct 1i, undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_NullResult) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* c = b.Construct(str_ty, 1_i, 2_u);
c->SetResult(nullptr);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:8:5 error: construct: result is undefined
undef = construct 1i, 2u
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_EmptyResult) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.i32()},
{mod.symbols.New("b"), ty.u32()},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* c = b.Construct(str_ty, 1_i, 2_u);
c->ClearResults();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:8:13 error: construct: expected exactly 1 results, got 0
undef = construct 1i, 2u
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_Texture) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32()));
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
R"(:3:26 error: construct: type is not constructible
%2:texture_2d<f32> = construct
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_TextureInStruct_WithCapability) {
auto* tex_ty = ty.sampled_texture(core::type::TextureDimension::k2d, ty.f32());
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), tex_ty},
});
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(str_ty);
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllowPointersAndHandlesInStructures});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Construct_NonConstructible_WithStructCapability) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Construct(ty.void_());
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllowPointersAndHandlesInStructures});
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
R"(:3:15 error: construct: type is not constructible
%2:void = construct
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_MissingArg) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* c = b.Convert(ty.i32(), 1_f);
c->ClearOperands();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:14 error: convert: expected exactly 1 operands, got 0
%2:i32 = convert
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_NullArg) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Convert(ty.i32(), nullptr);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:22 error: convert: operand is undefined
%2:i32 = convert undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_MissingResult) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* c = b.Convert(ty.i32(), 1_f);
c->ClearResults();
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:13 error: convert: expected exactly 1 results, got 0
undef = convert 1.0f
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_NullResult) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* c = b.Convert(ty.i32(), 1_f);
c->SetResult(nullptr);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: convert: result is undefined
undef = convert 1.0f
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_ScalarToScalar) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.f32());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.i32(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_ScalarIdentity) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.f32());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.f32(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'f32' -> 'f32'
%3:f32 = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_ScalarToVec) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.f32());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.vec2<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:20 error: convert: No defined converter for 'f32' -> 'vec2<f32>'
%3:vec2<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_ScalarToMat) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.f32());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.mat2x3<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:22 error: convert: No defined converter for 'f32' -> 'mat2x3<f32>'
%3:mat2x3<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_VecToVec) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.vec2<u32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.vec2<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_VecIdentity) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.vec3<f32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.vec3<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:20 error: convert: No defined converter for 'vec3<f32>' -> 'vec3<f32>'
%3:vec3<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_VecToVec_WidthMismatch) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.vec2<u32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.vec4<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:20 error: convert: No defined converter for 'vec2<u32>' -> 'vec4<f32>'
%3:vec4<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_VecToScalar) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.vec2<u32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.f32(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'vec2<u32>' -> 'f32'
%3:f32 = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_VecToMat) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.vec2<u32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.mat3x2<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:22 error: convert: No defined converter for 'vec2<u32>' -> 'mat3x2<f32>'
%3:mat3x2<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_MatToMat) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.mat4x4<f32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.mat4x4<f16>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_MatToMatIdentity) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.mat3x3<f32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.mat3x3<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:22 error: convert: No defined converter for 'mat3x3<f32>' -> 'mat3x3<f32>'
%3:mat3x3<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_MatToMat_ShapeMismatch) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.mat4x4<f32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.mat2x2<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:22 error: convert: No defined converter for 'mat4x4<f32>' -> 'mat2x2<f32>'
%3:mat2x2<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_MatToScalar) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.mat4x4<f32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.f32(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'mat4x4<f32>' -> 'f32'
%3:f32 = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_MatToVec) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.mat4x4<f32>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.vec4<f32>(), p);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:20 error: convert: No defined converter for 'mat4x4<f32>' -> 'vec4<f32>'
%3:vec4<f32> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_4xU8ToU32) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.vec4<u8>());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.u32(), p);
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllow8BitIntegers});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:14 error: convert: No defined converter for 'vec4<u8>' -> 'u32'
%3:u32 = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_U32To4xU8) {
auto* f = b.Function("f", ty.void_());
auto* p = b.FunctionParam("p", ty.u32());
f->AppendParam(p);
b.Append(f->Block(), [&] {
b.Convert(ty.vec4<u8>(), p);
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{Capability::kAllow8BitIntegers});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:19 error: convert: No defined converter for 'u32' -> 'vec4<u8>'
%3:vec4<u8> = convert %p
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_PtrToVal) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* v = b.Var("v", 0_u);
b.Convert(ty.u32(), v->Result());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:14 error: convert: No defined converter for 'ptr<function, u32, read_write>' -> 'u32'
%3:u32 = convert %v
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Convert_PtrToPtr) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
auto* v = b.Var("v", 0_u);
b.Convert(v->Result()->Type(), v->Result());
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:41 error: convert: not defined for result type, 'ptr<function, u32, read_write>'
%3:ptr<function, u32, read_write> = convert %v
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Block_NoTerminator) {
b.Function("my_func", ty.void_());
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:2:3 error: block does not end in a terminator instruction
$B1: {
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Block_VarBlockMismatch) {
auto* var = b.Var(ty.ptr<function, i32>());
auto* f = b.Function("f", ty.void_());
f->Block()->Append(var);
f->Block()->Append(b.Return(f));
auto* g = b.Function("g", ty.void_());
g->Block()->Append(b.Return(g));
var->SetBlock(g->Block());
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:41 error: var: block instruction does not have same block as parent
%2:ptr<function, i32, read_write> = var undef
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Block_DeadParameter) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.BlockParam("my_param", ty.f32());
b.Append(f->Block(), [&] {
auto* l = b.Loop();
b.Append(l->Initializer(), [&] { b.NextIteration(l, nullptr); });
l->Body()->SetParams({p});
b.Append(l->Body(), [&] { b.ExitLoop(l); });
b.Return(f);
});
p->Destroy();
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:7:12 error: destroyed parameter found in block parameter list
$B3 (%my_param:f32): { # body
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Block_ParameterWithNullBlock) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.BlockParam("my_param", ty.f32());
b.Append(f->Block(), [&] {
auto* l = b.Loop();
b.Append(l->Initializer(), [&] { b.NextIteration(l, nullptr); });
l->Body()->SetParams({p});
b.Append(l->Body(), [&] { b.ExitLoop(l); });
b.Return(f);
});
p->SetBlock(nullptr);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:7:12 error: block parameter has nullptr parent block
$B3 (%my_param:f32): { # body
^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Block_ParameterUsedInMultipleBlocks) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.BlockParam("my_param", ty.f32());
b.Append(f->Block(), [&] {
auto* l = b.Loop();
b.Append(l->Initializer(), [&] { b.NextIteration(l, nullptr); });
l->Body()->SetParams({p});
b.Append(l->Body(), [&] { b.Continue(l, p); });
l->Continuing()->SetParams({p});
b.Append(l->Continuing(), [&] { b.NextIteration(l, p); });
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:7:12 error: block parameter has incorrect parent block
$B3 (%my_param:f32): { # body
^^^^^^^^^
:10:7 note: parent block declared here
$B4 (%my_param:f32): { # continuing
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Instruction_AppendedDead) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* v = sb.Var(ty.ptr<function, f32>());
auto* ret = sb.Return(f);
v->Destroy();
v->InsertBefore(ret);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: destroyed instruction found in instruction list
<destroyed tint::core::ir::Var)"));
}
TEST_F(IR_ValidatorTest, Instruction_NullInstructionResultInstruction) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* v = sb.Var(ty.ptr<function, f32>());
sb.Return(f);
v->Result()->SetInstruction(nullptr);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: var: result instruction is undefined
%2:ptr<function, f32, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Instruction_DuplicateResultOneCall) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* l = b.Loop();
auto* r1 = b.InstructionResult(ty.u32());
l->SetResults(Vector{r1, r1});
b.Append(l->Body(), [&] { b.Unreachable(); });
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:13 error: loop: result was seen previously as a result
%2:u32, %2:u32 = loop [b: $B2] { # loop_1
^^^^^^)"))
<< res.Failure();
}
TEST_F(IR_ValidatorTest, Instruction_WrongInstructionResultInstruction) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* v = sb.Var(ty.ptr<function, f32>());
auto* v2 = sb.Var(ty.ptr<function, f32>());
sb.Return(f);
v->SetResult(v2->Result());
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:4:5 error: var: result instruction does not match instruction (possible double usage)
%2:ptr<function, f32, read_write> = var undef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Instruction_TooManyResultInstruction) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* v = sb.Var(ty.ptr<function, f32>());
v->SetResults(Vector{v->Results()[0], v->Results()[0]});
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(
R"(:3:76 error: var: expected exactly 1 results, got 2
%2:ptr<function, f32, read_write>, %2:ptr<function, f32, read_write> = var undef
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Instruction_DeadOperand) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* v = sb.Var(ty.ptr<function, f32>());
sb.Return(f);
auto* result = sb.InstructionResult(ty.f32());
result->Destroy();
v->SetInitializer(result);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:45 error: var: operand is not alive
%2:ptr<function, f32, read_write> = var %3
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Instruction_OperandUsageRemoved) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* v = sb.Var(ty.ptr<function, f32>());
sb.Return(f);
auto* result = sb.InstructionResult(ty.f32());
v->SetInitializer(result);
result->RemoveUsage({v, 0u});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:45 error: var: operand missing usage
%2:ptr<function, f32, read_write> = var %3
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Instruction_OrphanedInstruction) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* v = sb.Var(ty.ptr<function, f32>());
auto* load = sb.Load(v);
sb.Return(f);
load->Remove();
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(error: load: orphaned instruction: load
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_LHS_Nullptr) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
sb.Add(ty.i32(), nullptr, sb.Constant(2_i));
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:18 error: binary: operand is undefined
%2:i32 = add undef, 2i
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_RHS_Nullptr) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
sb.Add(ty.i32(), sb.Constant(2_i), nullptr);
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:22 error: binary: operand is undefined
%2:i32 = add 2i, undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_Result_Nullptr) {
auto* bin = mod.CreateInstruction<ir::CoreBinary>(nullptr, BinaryOp::kAdd, b.Constant(3_i),
b.Constant(2_i));
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
sb.Append(bin);
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: binary: result is undefined
undef = add 3i, 2i
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_MissingOperands) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* add = sb.Add(ty.i32(), sb.Constant(1_i), sb.Constant(2_i));
add->ClearOperands();
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: binary: expected exactly 2 operands, got 0
%2:i32 = add
^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_MissingResult) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* add = sb.Add(ty.i32(), sb.Constant(1_i), sb.Constant(2_i));
add->ClearResults();
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: binary: expected exactly 1 results, got 0
undef = add 1i, 2i
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_LogicalOr) {
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Binary(core::BinaryOp::kLogicalOr, ty.bool_(), b.Constant(true), b.Constant(true));
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: binary: logical-or is not valid in the IR
%2:bool = logical-or true, true
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_LogicalAnd) {
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Binary(core::BinaryOp::kLogicalAnd, ty.bool_(), b.Constant(false), b.Constant(false));
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: binary: logical-and is not valid in the IR
%2:bool = logical-and false, false
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_Valid) {
auto* i32 = ty.i32();
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Add(i32, b.Constant(1_i), b.Constant(2_i));
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success);
}
TEST_F(IR_ValidatorTest, Binary_TooManyOperands) {
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
auto* add = b.Add(ty.i32(), b.Constant(1_i), b.Constant(2_i));
add->PushOperand(b.Constant(3_i));
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: binary: expected exactly 2 operands, got 3
%2:i32 = add 1i, 2i, 3i
^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Binary_OperandWrongType_Func) {
auto* i32 = ty.i32();
auto* func = b.Function("foo", ty.void_());
auto* other_func = b.Function("other", ty.void_());
b.Append(other_func->Block(), [&] { b.Return(other_func); });
b.Append(func->Block(), [&] {
b.Add(i32, b.Constant(1_i), other_func);
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:3:5 error: binary: no matching overload for 'operator + (i32, <function>)'
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Unary_Value_Nullptr) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
sb.Negation(ty.i32(), nullptr);
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:23 error: unary: operand is undefined
%2:i32 = negation undef
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Unary_Result_Nullptr) {
auto* bin = mod.CreateInstruction<ir::CoreUnary>(nullptr, UnaryOp::kNegation, b.Constant(2_i));
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
sb.Append(bin);
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: unary: result is undefined
undef = negation 2i
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Unary_ResultTypeNotMatchValueType) {
auto* bin = b.Complement(ty.f32(), 2_i);
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
sb.Append(bin);
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:5 error: unary: result value type 'f32' does not match complement result type 'i32'
%2:f32 = complement 2i
^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Unary_MissingOperands) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* u = b.Negation(ty.f32(), 2_f);
u->ClearOperands();
sb.Append(u);
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: unary: expected exactly 1 operands, got 0
%2:f32 = negation
^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Unary_MissingResults) {
auto* f = b.Function("my_func", ty.void_());
auto sb = b.Append(f->Block());
auto* u = b.Negation(ty.f32(), 2_f);
u->ClearResults();
sb.Append(u);
sb.Return(f);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: unary: expected exactly 1 results, got 0
undef = negation 2.0f
^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Unary_Valid) {
auto* i32 = ty.i32();
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Negation(i32, b.Constant(1_i));
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success);
}
TEST_F(IR_ValidatorTest, Unary_TooManyOperands) {
auto* i32 = ty.i32();
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
// Manually create a negation with an extra operand.
auto* neg = b.Negation(i32, b.Constant(1_i));
neg->PushOperand(b.Constant(2_i));
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:5 error: unary: expected exactly 1 operands, got 2
%2:i32 = negation 1i, 2i
^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Unary_OperandWrongType) {
auto* i32 = ty.i32();
auto* other_func = b.Function("other", ty.void_());
b.Append(other_func->Block(), [&] { b.Return(other_func); });
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Negation(i32, other_func);
b.Return(func);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:8:5 error: unary: no matching overload for 'operator - (<function>)'
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Scoping_UseBeforeDecl) {
auto* f = b.Function("my_func", ty.void_());
auto* y = b.Add<i32>(2_i, 3_i);
auto* x = b.Add<i32>(y, 1_i);
f->Block()->Append(x);
f->Block()->Append(y);
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:18 error: binary: %3 is not in scope
%2:i32 = add %3, 1i
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Scoping_UseAfterNestedDecl_InTerminator) {
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
auto* outer = b.If(true);
outer->AddResult(b.InstructionResult<u32>());
b.Append(outer->True(), [&] {
Let* decl = nullptr;
auto* inner = b.If(true);
b.Append(inner->True(), [&] {
decl = b.Let("decl", 42_u);
b.ExitIf(inner);
});
b.ExitIf(outer, decl);
});
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:11:17 error: exit_if: %decl is not in scope
exit_if %decl # if_1
^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideWithoutCapability) {
b.Append(mod.root_block, [&] { b.Override("a", 1_u); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:2:12 error: override: root block: invalid instruction: tint::core::ir::Override
%a:u32 = override 1u
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, InstructionInRootBlockWithoutOverrideCap) {
b.Append(mod.root_block, [&] { b.Add(ty.u32(), 3_u, 2_u); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:2:3 error: binary: root block: invalid instruction: tint::core::ir::CoreBinary
%1:u32 = add 3u, 2u
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideWithCapability) {
b.Append(mod.root_block, [&] {
auto* o = b.Override(ty.u32());
o->SetOverrideId(OverrideId{1});
});
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideWithValue) {
b.Append(mod.root_block, [&] {
auto* z = b.Override(ty.u32());
z->SetOverrideId(OverrideId{2});
auto* init = b.Add(ty.u32(), z, 2_u);
b.Override("a", init);
});
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideWithInvalidType) {
b.Append(mod.root_block, [&] { b.Override(ty.vec3<u32>()); });
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:2:18 error: override: override type 'vec3<u32>' is not a scalar
%1:vec3<u32> = override undef
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideWithMismatchedInitializerType) {
b.Append(mod.root_block, [&] {
auto* init = b.Constant(1_i);
auto* o = b.Override(ty.u32());
o->SetInitializer(init);
});
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:2:12 error: override: override type 'u32' does not match initializer type 'i32'
%1:u32 = override 1i
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideDuplicateId) {
b.Append(mod.root_block, [&] {
auto* o = b.Override(ty.u32());
o->SetOverrideId(OverrideId{2});
auto* o2 = b.Override(ty.i32());
o2->SetOverrideId(OverrideId{2});
});
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:3:12 error: override: duplicate override id encountered: 2
%2:i32 = override undef @id(2)
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, InstructionInRootBlockOnlyUsedInRootBlock) {
core::ir::Value* init = nullptr;
b.Append(mod.root_block, [&] {
auto* z = b.Override(ty.u32());
z->SetOverrideId(OverrideId{2});
init = b.Add(ty.u32(), z, 2_u)->Result();
b.Override("a", init);
});
auto* f = b.Function("my_func", ty.void_());
b.Append(f->Block(), [&] {
b.Add(ty.u32(), init, 2_u);
b.Return(f);
});
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:3:3 error: binary: root block: instruction used outside of root block tint::core::ir::CoreBinary
%2:u32 = add %1, 2u
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideArrayInvalidValue) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
o = b.Override(ty.u32());
auto* c1 = ty.Get<core::ir::type::ValueArrayCount>(o->Result());
auto* a1 = ty.Get<core::type::Array>(ty.i32(), c1, 4u);
b.Var("a", ty.ptr(workgroup, a1, read_write));
});
o->Destroy();
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:2:51 error: var: %2 is not in scope
%a:ptr<workgroup, array<i32, %2>, read_write> = var undef
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, OverrideWithoutIdOrInitializer) {
b.Append(mod.root_block, [&] { b.Override(ty.u32()); });
auto res = ir::Validate(mod, core::ir::Capabilities{core::ir::Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:2:12 error: override: must have an id or an initializer
%1:u32 = override undef
^^^^^^^^
)")) << res.Failure();
}
} // namespace tint::core::ir