blob: 26a02285dfcef7cf0373bbe4c218292fdaab403e [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 "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"
#include "src/tint/lang/core/type/struct.h"
namespace tint::core::ir {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
TEST_F(IR_ValidatorTest, Function) {
auto* f = b.Function("my_func", ty.void_());
f->SetParams({b.FunctionParam(ty.i32()), b.FunctionParam(ty.f32())});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_NoType) {
auto* valid = b.Function("valid", ty.void_());
valid->Block()->Append(b.Return(valid));
auto* invalid = mod.CreateValue<ir::Function>(nullptr, ty.void_());
invalid->SetBlock(mod.blocks.Create<ir::Block>());
mod.SetName(invalid, "invalid");
mod.functions.Push(invalid);
invalid->Block()->Append(b.Return(invalid));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:1 error: functions must have type '<function>'
%invalid = func():void {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Duplicate) {
auto* f = b.Function("my_func", ty.void_());
// Function would auto-push by the builder, so this adds a duplicate
mod.functions.Push(f);
f->SetParams({b.FunctionParam(ty.i32()), b.FunctionParam(ty.f32())});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: function %my_func added to module multiple times
%my_func = func(%2:i32, %3:f32):void {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_MultipleEntryPoints_WithCapability) {
auto* ep1 = ComputeEntryPoint("ep1");
ep1->Block()->Append(b.Return(ep1));
auto* ep2 = ComputeEntryPoint("ep2");
ep2->Block()->Append(b.Return(ep2));
auto res = ir::Validate(mod, Capabilities{
Capability::kAllowMultipleEntryPoints,
});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_MultipleEntryPoints_WithoutCapability) {
auto* ep1 = ComputeEntryPoint("ep1");
ep1->Block()->Append(b.Return(ep1));
auto* ep2 = ComputeEntryPoint("ep2");
ep2->Block()->Append(b.Return(ep2));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: a module with multiple entry points requires the AllowMultipleEntryPoints capability
%ep2 = @compute @workgroup_size(1u, 1u, 1u) func():void {
^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_DuplicateEntryPointNames) {
auto* c = ComputeEntryPoint("dup");
c->Block()->Append(b.Return(c));
auto* f = FragmentEntryPoint("dup");
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod, Capabilities{
Capability::kAllowMultipleEntryPoints,
});
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:1 error: entry point name 'dup' is not unique
%dup_1 = @fragment func():void { # %dup_1: 'dup'
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_MultinBlock) {
auto* f = b.Function("my_func", ty.void_());
f->SetBlock(b.MultiInBlock());
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: root block for function cannot be a multi-in block
%my_func = func():void {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_DeadParameter) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.f32());
f->SetParams({p});
f->Block()->Append(b.Return(f));
p->Destroy();
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:1:17 error: destroyed parameter found in function parameter list
%my_func = func(%my_param:f32):void {
^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterWithNullFunction) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.f32());
f->SetParams({p});
f->Block()->Append(b.Return(f));
p->SetFunction(nullptr);
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:17 error: function parameter has nullptr parent function
%my_func = func(%my_param:f32):void {
^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterUsedInMultipleFunctions) {
auto* p = b.FunctionParam("my_param", ty.f32());
auto* f1 = b.Function("my_func1", ty.void_());
auto* f2 = b.Function("my_func2", ty.void_());
f1->SetParams({p});
f2->SetParams({p});
f1->Block()->Append(b.Return(f1));
f2->Block()->Append(b.Return(f2));
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:18 error: function parameter has incorrect parent function
%my_func1 = func(%my_param:f32):void {
^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterWithNullType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", nullptr);
f->SetParams({p});
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: function parameter has nullptr type
%my_func = func(%my_param:undef):void {
^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterDuplicated) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.u32());
f->SetParams({p, p});
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: function parameter is not unique
%my_func = func(%my_param:u32%my_param:u32):void {
^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_MultipleIOAnnotations) {
auto* f = FragmentEntryPoint("my_func");
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
p->SetBuiltin(BuiltinValue::kPosition);
p->SetLocation(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:27 error: input param has more than one IO annotation, [ @location, built-in ]
%my_func = @fragment func(%my_param:vec4<f32> [@location(0), @position]):void {
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_MultipleIOAnnotations) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr;
attr.builtin = BuiltinValue::kPosition;
attr.color = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.vec4<f32>(), attr},
});
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:27 error: input param struct member has more than one IO annotation, [ built-in, @color ]
%my_func = @fragment func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_MissingIOAnnotations) {
auto* f = FragmentEntryPoint("my_func");
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:27 error: input param must have at least one IO annotation, e.g. a binding point, a location, etc
%my_func = @fragment func(%my_param:vec4<f32>):void {
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_MissingIOAnnotations) {
auto* f = ComputeEntryPoint("my_func");
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.vec4<f32>(), {}},
});
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:54 error: input param struct members must have at least one IO annotation, e.g. a binding point, a location, etc
%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_DuplicateAnnotations) {
auto* f = ComputeEntryPoint("my_func");
IOAttributes attr;
attr.builtin = BuiltinValue::kPosition;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.vec4<f32>(), attr},
});
auto* p = b.FunctionParam("my_param", str_ty);
p->SetBuiltin(BuiltinValue::kPosition);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:54 error: input param struct member has same IO annotation, as top-level struct, 'built-in'
%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct [@position]):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_WorkgroupPlusOtherIOAnnotation) {
auto* f = ComputeEntryPoint("my_func");
auto* p = b.FunctionParam("my_param", ty.ptr<workgroup, i32>());
p->SetLocation(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod, Capabilities{Capability::kAllowWorkspacePointerInputToEntryPoint});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:54 error: input param has more than one IO annotation, [ @location, <workgroup> ]
%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:ptr<workgroup, i32, read_write> [@location(0)]):void {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_WorkgroupPlusOtherIOAnnotations) {
auto* f = ComputeEntryPoint("my_func");
IOAttributes attr;
attr.location = 0;
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"),
{
{mod.symbols.New("a"), ty.ptr<workgroup, i32>(), attr},
});
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod, Capabilities{Capability::kAllowPointersAndHandlesInStructures});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:54 error: input param struct member has more than one IO annotation, [ @location, <workgroup> ]
%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Location_InvalidType) {
auto* f = FragmentEntryPoint("my_func");
auto* p = b.FunctionParam("my_param", ty.bool_());
p->SetLocation(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:27 error: fragment entry point params can only be a bool if decorated with @builtin(front_facing)
%my_func = @fragment func(%my_param:bool [@location(0)]):void {
^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_Location_InvalidType) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr;
attr.location = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.bool_(), attr},
});
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:27 error: fragment entry point param members can only be a bool if decorated with @builtin(front_facing)
%my_func = @fragment func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Location_Struct_WithCapability) {
auto* f = FragmentEntryPoint("my_func");
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32()},
});
auto* p = b.FunctionParam("my_param", str_ty);
p->SetLocation(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod, Capabilities{Capability::kAllowLocationForNumericElements});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Location_Struct_WithoutCapability) {
auto* f = FragmentEntryPoint("my_func");
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32()},
});
auto* p = b.FunctionParam("my_param", str_ty);
p->SetLocation(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:27 error: input param with a location attribute must be a numeric scalar or vector, but has type MyStruct
%my_func = @fragment func(%my_param:MyStruct [@location(0)]):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_InputLocation_Duplicate_InParams) {
auto* f = FragmentEntryPoint("my_func");
auto* p1 = b.FunctionParam("p1", ty.f32());
p1->SetLocation(0);
auto* p2 = b.FunctionParam("p2", ty.f32());
p2->SetLocation(0);
f->SetParams({p1, p2});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:51 error: duplicate location(0) on entry point input
%my_func = @fragment func(%p1:f32 [@location(0)], %p2:f32 [@location(0)]):void {
^^^^^^^
:1:27 note: %p1 declared here
%my_func = @fragment func(%p1:f32 [@location(0)], %p2:f32 [@location(0)]):void {
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_InputLocation_Duplicate_InParamAndMSV) {
auto* f = FragmentEntryPoint("my_func");
auto* p1 = b.FunctionParam("p1", ty.f32());
p1->SetLocation(0);
f->SetParams({p1});
auto* v = b.Var("v", AddressSpace::kIn, ty.f32());
v->SetLocation(0);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
b.Load(v);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:2:29 error: var: duplicate location(0) on entry point input
%v:ptr<__in, f32, read> = var undef @location(0)
^^^
:5:27 note: %p1 declared here
%my_func = @fragment func(%p1:f32 [@location(0)]):void {
^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_OutputLocation_Duplicate_InReturnAndMSV) {
auto* f = FragmentEntryPoint("my_func");
f->SetReturnType(ty.f32());
f->SetReturnLocation(0);
auto* v = b.Var("v", AddressSpace::kOut, ty.f32());
v->SetLocation(0);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
b.Store(v, 1.0_f);
b.Return(f, 1.0_f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:2:36 error: var: duplicate location(0) on entry point output
%v:ptr<__out, f32, read_write> = var undef @location(0)
^^^
:5:1 note: %my_func declared here
%my_func = @fragment func():f32 [@location(0)] {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_OutputLocation_Duplicate_InReturnStruct) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr;
attr.location = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr},
{mod.symbols.New("b"), ty.f32(), attr},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:1 error: duplicate location(0) on entry point output
%my_func = @fragment func():MyStruct {
^^^^^^^^
:6:1 note: %my_func declared here
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_Compute_InputLocation_InParam) {
auto* f = ComputeEntryPoint("my_func");
auto* p = b.FunctionParam("p", ty.f32());
p->SetLocation(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:1:54 error: location attribute is not valid for compute shader inputs
%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%p:f32 [@location(0)]):void {
^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_Compute_InputLocation_InMSV) {
auto* f = ComputeEntryPoint("my_func");
auto* v = b.Var("v", AddressSpace::kIn, ty.f32());
v->SetLocation(0);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
b.Load(v);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:2:29 error: var: location attribute is not valid for compute shader inputs
%v:ptr<__in, f32, read> = var undef @location(0)
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_Compute_OutputLocation) {
auto* f = ComputeEntryPoint("my_func");
auto* v = b.Var("v", AddressSpace::kOut, ty.f32());
v->SetLocation(0);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
b.Store(v, 1.0_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_SameLocation_InputAndOutput) {
auto* f = FragmentEntryPoint("my_func");
auto* p = b.FunctionParam("p", ty.f32());
p->SetLocation(0);
f->SetParams({p});
f->SetReturnType(ty.f32());
f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Return(f, 1.0_f); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_SameLocation_DifferentEntryPoints) {
auto* f1 = FragmentEntryPoint("f1");
auto* p1 = b.FunctionParam("p1", ty.f32());
p1->SetLocation(0);
f1->SetParams({p1});
b.Append(f1->Block(), [&] { b.Return(f1); });
auto* f2 = FragmentEntryPoint("f2");
auto* p2 = b.FunctionParam("p2", ty.f32());
p2->SetLocation(0);
f2->SetParams({p2});
b.Append(f2->Block(), [&] { b.Return(f2); });
auto res = ir::Validate(mod, Capabilities{Capability::kAllowMultipleEntryPoints});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_Valid) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_TooManyLocations) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
IOAttributes attr2;
attr2.location = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
{mod.symbols.New("c"), ty.f32(), attr2},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:7:1 error: structs with blend_src members must have exactly 2 members with location annotations
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_Input) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
auto* p = b.FunctionParam("p", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:6:27 error: blend_src can only be used on fragment shader outputs
%my_func = @fragment func(%p:MyStruct):void {
^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_NotFragment) {
auto* f = ComputeEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:6:1 error: blend_src can only be used on fragment shader outputs
%my_func = @compute @workgroup_size(1u, 1u, 1u) func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_WrongMemberCount) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {{mod.symbols.New("a"), ty.f32(), attr0}});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: structs with blend_src members must have exactly 2 members with location annotations
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_WrongLocation) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 1;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 1;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: struct members with blend_src must be located at 0
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_MissingLocation) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: struct members with blend_src must be located at 0
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_DifferentMemberTypes) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.vec4<f32>(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: blend_src type f32 does not match other blend_src type vec4<f32>
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_InvalidMemberType) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.bool_(), attr0},
{mod.symbols.New("b"), ty.bool_(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: blend_src must be a numeric scalar or vector, but has type bool
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_MissingBlendSrc0) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr;
attr.location = 0;
attr.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr},
{mod.symbols.New("b"), ty.f32(), attr},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: if any @blend_src is used on an output, then @blend_src(0) and @blend_src(1) must be used
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_MissingBlendSrc1) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr;
attr.location = 0;
attr.blend_src = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr},
{mod.symbols.New("b"), ty.f32(), attr},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: if any @blend_src is used on an output, then @blend_src(0) and @blend_src(1) must be used
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_InvalidBlendSrcValue) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 2;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:1 error: blend_src value must be 0 or 1
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_DuplicateBlendSrcValue) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:6:1 error: duplicate blend_src(0) on entry point output
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_DuplicateLocation_Unused) {
auto* f = FragmentEntryPoint("my_func");
// A valid blend_src struct at location 0.
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
// Another output variable also at location 0.
auto* v = b.Var("v", AddressSpace::kOut, ty.f32());
v->SetLocation(0);
mod.root_block->Append(v);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_DuplicateLocation_Used) {
auto* f = FragmentEntryPoint("my_func");
// A valid blend_src struct at location 0.
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
{mod.symbols.New("b"), ty.f32(), attr1},
});
f->SetReturnType(str_ty);
// Another output variable also at location 0.
auto* v = b.Var("v", AddressSpace::kOut, ty.f32());
v->SetLocation(0);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
b.Store(v, 1.0_f);
b.Unreachable();
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:7:36 error: var: duplicate location(0) on entry point output
%v:ptr<__out, f32, read_write> = var undef @location(0)
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_PartialStruct) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr;
attr.location = 0;
attr.blend_src = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr},
});
f->SetReturnType(str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: structs with blend_src members must have exactly 2 members with location annotations
%my_func = @fragment func():MyStruct {
^^^^^^^^
:5:1 error: if any @blend_src is used on an output, then @blend_src(0) and @blend_src(1) must be used
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_PartialStructAndMSV) {
auto* f = FragmentEntryPoint("my_func");
IOAttributes attr0;
attr0.location = 0;
attr0.blend_src = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), attr0},
});
f->SetReturnType(str_ty);
// MSV for the missing blend_src
IOAttributes attr1;
attr1.location = 0;
attr1.blend_src = 1;
auto* v = b.Var("v", AddressSpace::kOut, ty.f32());
v->SetAttributes(attr1);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
b.Store(v, 1.0_f);
b.Unreachable();
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:9:1 error: structs with blend_src members must have exactly 2 members with location annotations
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_NonMember_WithoutCapability) {
auto* f = FragmentEntryPoint("my_func");
auto* var0 = b.Var("var0", ty.ptr(AddressSpace::kOut, ty.f32()));
var0->SetLocation(0);
var0->SetBlendSrc(0);
mod.root_block->Append(var0);
auto* var1 = b.Var("var1", ty.ptr(AddressSpace::kOut, ty.f32()));
var1->SetLocation(0);
var1->SetBlendSrc(1);
mod.root_block->Append(var1);
b.Append(f->Block(), [&] {
b.Store(var0, 1_f);
b.Store(var1, 1_f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:2:39 error: var: blend_src cannot be used on non-struct-member types
%var0:ptr<__out, f32, read_write> = var undef @location(0) @blend_src(0)
^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, EntryPoint_BlendSrc_NonMember_WithCapability) {
auto* f = FragmentEntryPoint("my_func");
auto* var0 = b.Var("var0", ty.ptr(AddressSpace::kOut, ty.f32()));
var0->SetLocation(0);
var0->SetBlendSrc(0);
mod.root_block->Append(var0);
auto* var1 = b.Var("var1", ty.ptr(AddressSpace::kOut, ty.f32()));
var1->SetLocation(0);
var1->SetBlendSrc(1);
mod.root_block->Append(var1);
b.Append(f->Block(), [&] {
b.Store(var0, 1_f);
b.Store(var1, 1_f);
b.Return(f);
});
auto res = ir::Validate(mod, Capabilities{
Capability::kLoosenValidationForShaderIO,
});
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterWithConstructibleType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.u32());
f->SetParams({p});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterWithPointerType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.ptr<function, i32>());
f->SetParams({p});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterWithTextureType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.external_texture());
f->SetParams({p});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterWithSamplerType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.sampler());
f->SetParams({p});
f->Block()->Append(b.Return(f));
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_ParameterWithVoidType) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.void_());
f->SetParams({p});
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: function parameter type, 'void', must be constructible, a pointer, or a handle
%my_func = func(%my_param:void):void {
^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_InvariantWithPosition) {
auto* f = b.Function("my_func", ty.void_(), Function::PipelineStage::kFragment);
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
p->SetInvariant(true);
p->SetBuiltin(BuiltinValue::kPosition);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_InvariantWithoutPosition) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
p->SetInvariant(true);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:17 error: invariant can only decorate a param if it is also decorated with position
%my_func = func(%my_param:vec4<f32> [@invariant]):void {
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_InvariantWithPosition) {
auto* f = b.Function("my_func", ty.void_(), Function::PipelineStage::kFragment);
IOAttributes attr;
attr.invariant = true;
attr.builtin = BuiltinValue::kPosition;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("pos"), ty.vec4<f32>(), attr},
});
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_InvariantWithoutPosition) {
IOAttributes attr;
attr.invariant = true;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("pos"), ty.vec4<f32>(), attr},
});
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:17 error: invariant can only decorate a param member if it is also decorated with position
%my_func = func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_StructNested_InvariantWithoutPosition) {
IOAttributes attr;
attr.invariant = true;
auto* inner_ty =
ty.Struct(mod.symbols.New("Inner"), {{mod.symbols.New("pos"), ty.vec4<f32>(), attr}});
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {{mod.symbols.New("i"), inner_ty}});
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:9:17 error: invariant can only decorate a param member if it is also decorated with position
%my_func = func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Color_NonFragment) {
auto* f = b.ComputeFunction("my_func");
auto* p = b.FunctionParam("my_param", ty.vec4<f32>());
p->SetColor(0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:1:54 error: @color can only be used on fragment shader inputs
%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:vec4<f32> [@color(0)]):void {
^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_Struct_Color_NonFragment) {
IOAttributes attr;
attr.color = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("pos"), ty.vec4<f32>(), attr},
});
auto* f = b.ComputeFunction("my_func");
auto* p = b.FunctionParam("my_param", str_ty);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:5:54 error: @color can only be used on fragment shader inputs
%my_func = @compute @workgroup_size(1u, 1u, 1u) func(%my_param:MyStruct):void {
^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Param_BindingPointWithoutCapability) {
auto* f = b.Function("my_func", ty.void_());
auto* p = b.FunctionParam("my_param", ty.ptr<uniform, i32>());
p->SetBindingPoint(0, 0);
f->SetParams({p});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:1:17 error: input param to non-entry point function has a binding point set
%my_func = func(%my_param:ptr<uniform, i32, read> [@binding_point(0, 0)]):void {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_MultipleIOAnnotations) {
auto* f = VertexEntryPoint("my_func");
f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:1 error: return value has more than one IO annotation, [ @location, built-in ]
%my_func = @vertex func():vec4<f32> [@location(0), @position] {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_Struct_MultipleIOAnnotations) {
IOAttributes attr;
attr.builtin = BuiltinValue::kPosition;
attr.location = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.vec4<f32>(), attr},
});
auto* f = b.Function("my_func", str_ty, Function::PipelineStage::kVertex);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: return value struct member has more than one IO annotation, [ @location, built-in ]
%my_func = @vertex func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_Location_InvalidType) {
auto* f = FragmentEntryPoint("my_func");
f->SetReturnType(ty.bool_());
f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:1 error: return value with a location attribute must be a numeric scalar or vector, but has type bool
%my_func = @fragment func():bool [@location(0)] {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_Struct_Location_InvalidType) {
IOAttributes attr;
attr.location = 0;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.bool_(), attr},
});
auto* f = b.Function("my_func", str_ty, Function::PipelineStage::kFragment);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: return value struct member with a location attribute must be a numeric scalar or vector, but has type bool
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_Void_IOAnnotation) {
auto* f = FragmentEntryPoint();
f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: return value with void type should never be annotated
%f = @fragment func():void [@location(0)] {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_NonVoid_MissingIOAnnotations) {
auto* f = b.Function("my_func", ty.f32(), Function::PipelineStage::kFragment);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:1 error: return value must have at least one IO annotation, e.g. a binding point, a location, etc
%my_func = @fragment func():f32 {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_NonVoid_Struct_MissingIOAnnotations) {
auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.f32(), {}},
});
auto* f = b.Function("my_func", str_ty, Function::PipelineStage::kFragment);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: return value struct members must have at least one IO annotation, e.g. a binding point, a location, etc
%my_func = @fragment func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_InvariantWithPosition) {
auto* f = b.Function("my_func", ty.vec4<f32>(), Function::PipelineStage::kVertex);
f->SetReturnBuiltin(BuiltinValue::kPosition);
f->SetReturnInvariant(true);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_InvariantWithoutPosition) {
auto* f = b.Function("my_func", ty.vec4<f32>());
f->SetReturnInvariant(true);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:1 error: invariant can only decorate outputs if they are also position builtins
%my_func = func():vec4<f32> [@invariant] {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_Struct_InvariantWithPosition) {
IOAttributes attr;
attr.invariant = true;
attr.builtin = BuiltinValue::kPosition;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("pos"), ty.vec4<f32>(), attr},
});
auto* f = b.Function("my_func", str_ty, Function::PipelineStage::kVertex);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Return_Struct_InvariantWithoutPosition) {
IOAttributes attr;
attr.invariant = true;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("pos"), ty.vec4<f32>(), attr},
});
auto* f = b.Function("my_func", str_ty);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: invariant can only decorate output members if they are also position builtins
%my_func = func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_UnnamedEntryPoint) {
auto* f = b.Function(ty.void_(), ir::Function::PipelineStage::kCompute);
f->SetWorkgroupSize({b.Constant(1_u), b.Constant(1_u), b.Constant(1_u)});
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:1:1 error: entry points must have names
%1 = @compute @workgroup_size(1u, 1u, 1u) func():void {
^^
)")) << res.Failure();
}
// Parameterizing these tests is very difficult/unreadable due to the
// multiple layers of forwarding/templating occurring in the type manager, so
// writing them out explicitly instead.
TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_ExternalTexture) {
auto* f = b.Function(ty.external_texture());
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: function return type must be constructible
%1 = func():texture_external {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Sampler) {
auto* f = b.Function(ty.sampler());
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: function return type must be constructible
%1 = func():sampler {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_RuntimeArray) {
auto* f = b.Function(ty.runtime_array(ty.f32()));
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: function return type must be constructible
%1 = func():array<f32> {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Ptr) {
auto* f = b.Function(ty.ptr<function, i32>());
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: function return type must be constructible
%1 = func():ptr<function, i32, read_write> {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_NonConstructibleReturnType_Ref) {
auto* f = b.Function(ty.ref<function, u32>());
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: function return type must be constructible
%1 = func():ref<function, u32, read_write> {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Compute_NonVoidReturn) {
auto* f = b.Function("my_func", ty.f32(), core::ir::Function::PipelineStage::kCompute);
f->SetWorkgroupSize(b.Constant(1_u), b.Constant(1_u), b.Constant(1_u));
f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:1:1 error: compute entry point must not have a return type, found 'f32'
%my_func = @compute @workgroup_size(1u, 1u, 1u) func():f32 [@location(0)] {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_MissingOnCompute) {
auto* f = b.Function("f", ty.void_(), Function::PipelineStage::kCompute);
b.Append(f->Block(), [&] { b.Return(f); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: compute entry point requires @workgroup_size
%f = @compute func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_NonCompute) {
auto* f = FragmentEntryPoint();
f->SetWorkgroupSize(b.Constant(1_u), b.Constant(1_u), b.Constant(1_u));
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: @workgroup_size only valid on compute entry point
%f = @fragment @workgroup_size(1u, 1u, 1u) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamUndefined) {
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({nullptr, b.Constant(2_u), b.Constant(3_u)});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: a @workgroup_size param is undefined or missing a type
%f = @compute @workgroup_size(undef, 2u, 3u) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamWrongType) {
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({b.Constant(1_f), b.Constant(2_u), b.Constant(3_u)});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:1:1 error: @workgroup_size params must be an 'i32' or 'u32', received 'f32'
%f = @compute @workgroup_size(1.0f, 2u, 3u) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamsSameType) {
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({b.Constant(1_u), b.Constant(2_i), b.Constant(3_u)});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: @workgroup_size params must be all 'i32's or all 'u32's
%f = @compute @workgroup_size(1u, 2i, 3u) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_InvalidValueKind) {
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({b.Constant(1_u), b.FunctionParam("p", ty.u32()), b.Constant(3_u)});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod, Capabilities{Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: @workgroup_size must be an InstructionResult or a Constant
%f = @compute @workgroup_size(1u, %p, 3u) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamsTooSmall) {
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({b.Constant(-1_i), b.Constant(2_i), b.Constant(3_i)});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: @workgroup_size params must be greater than 0
%f = @compute @workgroup_size(-1i, 2i, 3i) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamZero) {
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({b.Constant(0_i), b.Constant(2_i), b.Constant(3_i)});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: @workgroup_size params must be greater than 0
%f = @compute @workgroup_size(0i, 2i, 3i) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_ParamsTooLarge) {
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({b.Constant(1048576_i), b.Constant(1048576_i), b.Constant(1048576_i)});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: workgroup grid size cannot exceed 0xffffffff
%f = @compute @workgroup_size(1048576i, 1048576i, 1048576i) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_OverrideWithoutAllowOverrides) {
auto* o = b.Override(ty.u32());
auto* f = ComputeEntryPoint();
f->SetWorkgroupSize({o->Result(), o->Result(), o->Result()});
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:1 error: @workgroup_size param is not a constant value, and IR capability 'kAllowOverrides' is not set
%f = @compute @workgroup_size(%2, %2, %2) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_WorkgroupSize_NonRootBlockOverride) {
auto* f = ComputeEntryPoint();
Override* o;
b.Append(f->Block(), [&] {
o = b.Override(ty.u32());
b.Return(f);
});
f->SetWorkgroupSize({o->Result(), o->Result(), o->Result()});
auto res = ir::Validate(mod, Capabilities{Capability::kAllowOverrides});
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: @workgroup_size param defined by non-module scope value
%f = @compute @workgroup_size(%2, %2, %2) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Vertex_BasicPosition) {
auto* f = b.Function("my_func", ty.vec4<f32>(), Function::PipelineStage::kVertex);
f->SetReturnBuiltin(BuiltinValue::kPosition);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Vertex_StructPosition) {
auto pos_ty = ty.vec4<f32>();
auto pos_attr = IOAttributes();
pos_attr.builtin = BuiltinValue::kPosition;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("pos"), pos_ty, pos_attr},
});
auto* f = b.Function("my_func", str_ty, Function::PipelineStage::kVertex);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Vertex_StructPositionAndClipDistances) {
auto pos_ty = ty.vec4<f32>();
auto pos_attr = IOAttributes();
pos_attr.builtin = BuiltinValue::kPosition;
auto clip_ty = ty.array<f32, 4>();
auto clip_attr = IOAttributes();
clip_attr.builtin = BuiltinValue::kClipDistances;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("pos"), pos_ty, pos_attr},
{mod.symbols.New("clip"), clip_ty, clip_attr},
});
auto* f = b.Function("my_func", str_ty, Function::PipelineStage::kVertex);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_EQ(res, Success) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Vertex_StructOnlyClipDistances) {
auto clip_ty = ty.array<f32, 4>();
auto clip_attr = IOAttributes();
clip_attr.builtin = BuiltinValue::kClipDistances;
auto* str_ty =
ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("clip"), clip_ty, clip_attr},
});
auto* f = b.Function("my_func", str_ty, Function::PipelineStage::kVertex);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:5:1 error: position must be declared for vertex entry point output
%my_func = @vertex func():MyStruct {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Vertex_MissingPosition) {
auto* f = b.Function("my_func", ty.vec4<f32>(), Function::PipelineStage::kVertex);
f->SetReturnLocation(0);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: position must be declared for vertex entry point output
%my_func = @vertex func():vec4<f32> [@location(0)] {
^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_NonFragment_BoolInput) {
auto* f = VertexEntryPoint();
auto* p = b.FunctionParam("invalid", ty.bool_());
p->SetLocation(0);
f->AppendParam(p);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(
R"(:1:19 error: entry point params can only be a bool for fragment shaders
%f = @vertex func(%invalid:bool [@location(0)]):vec4<f32> [@position] {
^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_NonFragment_BoolOutput) {
auto* f = VertexEntryPoint();
IOAttributes attr;
attr.location = 0;
AddReturn(f, "invalid", ty.bool_(), attr);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:6:1 error: entry point return members can not be 'bool'
%f = @vertex func():OutputStruct {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Fragment_BoolInputWithoutFrontFacing) {
auto* f = FragmentEntryPoint();
auto* p = b.FunctionParam("invalid", ty.bool_());
p->SetLocation(0);
f->AppendParam(p);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:21 error: fragment entry point params can only be a bool if decorated with @builtin(front_facing)
%f = @fragment func(%invalid:bool [@location(0)]):void {
^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_Fragment_BoolOutput) {
auto* f = FragmentEntryPoint();
IOAttributes attr;
attr.location = 0;
AddReturn(f, "invalid", ty.bool_(), attr);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: entry point returns can not be 'bool'
%f = @fragment func():bool [@location(0)] {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_BoolOutput_via_MSV) {
auto* f = ComputeEntryPoint();
auto* v = b.Var(ty.ptr(AddressSpace::kOut, ty.bool_(), core::Access::kReadWrite));
v->SetLocation(0);
mod.root_block->Append(v);
b.Append(f->Block(), [&] {
b.Append(mod.CreateInstruction<ir::Store>(v->Result(), b.Constant(b.ConstantValue(false))));
b.Unreachable();
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: IO address space values referenced by shader entry points can only be 'bool' if in the input space, used only by fragment shaders and decorated with @builtin(front_facing)
%f = @compute @workgroup_size(1u, 1u, 1u) func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_BoolInputWithoutFrontFacing_via_MSV) {
auto* f = FragmentEntryPoint();
auto* invalid = b.Var("invalid", AddressSpace::kIn, ty.bool_());
invalid->SetLocation(0);
mod.root_block->Append(invalid);
b.Append(f->Block(), [&] {
auto* l = b.Load(invalid);
auto* v = b.Var("v", AddressSpace::kFunction, ty.bool_());
v->SetInitializer(l->Result());
b.Unreachable();
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:5:1 error: input address space values referenced by fragment shaders can only be 'bool' if decorated with @builtin(front_facing)
%f = @fragment func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_EntryPoint_PtrToWorkgroup) {
auto* f = FragmentEntryPoint();
auto* p = b.FunctionParam("invalid", ty.ptr<workgroup, i32>());
f->AppendParam(p);
b.Append(f->Block(), [&] { b.Unreachable(); });
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(
res.Failure().reason,
testing::HasSubstr(
R"(:1:21 error: input param to entry point cannot be a ptr in the 'workgroup' address space
%f = @fragment func(%invalid:ptr<workgroup, i32, read_write>):void {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_DirectRecursion) {
auto* f = b.Function("f", ty.void_());
b.Append(f->Block(), [&] {
b.Call(f);
b.Return(f);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: recursive function calls are not allowed
%f = func():void {
^^
)")) << res.Failure();
}
TEST_F(IR_ValidatorTest, Function_IndirectRecursion) {
auto* f1 = b.Function("f1", ty.void_());
auto* f2 = b.Function("f2", ty.void_());
auto* f3 = b.Function("f3", ty.void_());
b.Append(f1->Block(), [&] {
b.Call(f2);
b.Return(f1);
});
b.Append(f2->Block(), [&] {
b.Call(f3);
b.Return(f2);
});
b.Append(f3->Block(), [&] {
b.Call(f1);
b.Return(f3);
});
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_THAT(res.Failure().reason,
testing::HasSubstr(R"(:1:1 error: recursive function calls are not allowed
%f1 = func():void {
^^^
)")) << res.Failure();
}
} // namespace tint::core::ir