| // 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/ir/builder.h" |
| #include "src/tint/lang/core/ir/function_param.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" |
| |
| namespace tint::core::ir { |
| |
| using namespace tint::core::fluent_types; // NOLINT |
| using namespace tint::core::number_suffixes; // NOLINT |
| |
| TEST_F(IR_ValidatorTest, CallToFunctionOutsideModule) { |
| auto* f = b.Function("f", ty.void_()); |
| auto* g = b.Function("g", ty.void_()); |
| mod.functions.Pop(); // Remove g |
| |
| b.Append(f->Block(), [&] { |
| b.Call(g); |
| b.Return(f); |
| }); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:3:20 error: call: %g is not part of the module |
| %2:void = call %g |
| ^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToEntryPointFunction) { |
| auto* f = b.Function("f", ty.void_()); |
| auto* g = ComputeEntryPoint("g"); |
| |
| b.Append(f->Block(), [&] { |
| b.Call(g); |
| b.Return(f); |
| }); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:3:20 error: call: call target must not have a pipeline stage |
| %2:void = call %g |
| ^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToFunctionTooFewArguments) { |
| auto* g = b.Function("g", ty.void_()); |
| g->SetParams({b.FunctionParam<i32>(), b.FunctionParam<i32>()}); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| b.Call(g, 42_i); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr( |
| R"(:8:20 error: call: function has 2 parameters, but call provides 1 arguments |
| %5:void = call %g, 42i |
| ^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToFunctionTooManyArguments) { |
| auto* g = b.Function("g", ty.void_()); |
| g->SetParams({b.FunctionParam<i32>(), b.FunctionParam<i32>()}); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| b.Call(g, 1_i, 2_i, 3_i); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr( |
| R"(:8:20 error: call: function has 2 parameters, but call provides 3 arguments |
| %5:void = call %g, 1i, 2i, 3i |
| ^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToFunctionWrongArgType) { |
| auto* g = b.Function("g", ty.void_()); |
| g->SetParams({b.FunctionParam<i32>(), b.FunctionParam<i32>(), b.FunctionParam<i32>()}); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| b.Call(g, 1_i, 2_f, 3_i); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT( |
| res.Failure().reason, |
| testing::HasSubstr( |
| R"(:8:28 error: call: type 'i32' of function parameter 1 does not match argument type 'f32' |
| %6:void = call %g, 1i, 2.0f, 3i |
| ^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToFunctionNullArg) { |
| auto* g = b.Function("g", ty.void_()); |
| g->SetParams({b.FunctionParam<i32>()}); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| b.Call(g, nullptr); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:8:24 error: call: operand is undefined |
| %4:void = call %g, undef |
| ^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToNullFunction) { |
| auto* g = b.Function("g", ty.void_()); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(g); |
| c->SetOperands(Vector{static_cast<ir::Value*>(nullptr)}); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:8:20 error: call: operand is undefined |
| %3:void = call undef |
| ^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToFunctionNoResult) { |
| auto* g = b.Function("g", ty.void_()); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(g); |
| 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: call: expected exactly 1 results, got 0 |
| undef = call %g |
| ^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToFunction_ResultTypeMismatch) { |
| auto* f = b.Function("my_func", ty.i32()); |
| b.Append(f->Block(), [&] { b.Return(f, 42_i); }); |
| |
| auto* main = b.Function("main", ty.void_()); |
| b.Append(main->Block(), [&] { |
| b.Call(ty.u32(), f); |
| b.Return(main); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr( |
| R"(:8:14 error: call: result type does not match function return type |
| %3:u32 = call %my_func |
| ^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToFunctionNoOperands) { |
| auto* g = b.Function("g", ty.void_()); |
| b.Append(g->Block(), [&] { b.Return(g); }); |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(g); |
| c->ClearOperands(); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:8:15 error: call: expected at least 1 operands, got 0 |
| %3:void = call |
| ^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToNonFunctionTarget) { |
| auto* g = b.Function("g", ty.void_()); |
| mod.functions.Pop(); // Remove g, since it isn't actually going to be used, it is just |
| // needed to create the UserCall before mangling it |
| |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(g); |
| c->SetOperands(Vector{b.Value(0_i)}); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:3:20 error: call: target not defined or not a function |
| %2:void = call 0i |
| ^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_MissingResult) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(ty.f32(), BuiltinFn::kAbs, 1_f); |
| c->SetResults(Vector{static_cast<ir::InstructionResult*>(nullptr)}); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:3:5 error: abs: result is undefined |
| undef = abs 1.0f |
| ^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_MismatchResultType) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(ty.f32(), BuiltinFn::kAbs, 1_f); |
| c->Result()->SetType(ty.i32()); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT( |
| res.Failure().reason, |
| testing::HasSubstr( |
| R"(:3:14 error: abs: call result type 'i32' does not match builtin return type 'f32' |
| %2:i32 = abs 1.0f |
| ^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_MissingArg) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* i = b.Var<function, f32>("i"); |
| i->SetInitializer(b.Constant(0_f)); |
| auto* load = b.Load(i); |
| auto* c = b.Call(ty.f32(), BuiltinFn::kAbs, load->Result()); |
| c->SetOperands(Vector{static_cast<ir::Value*>(nullptr)}); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:5:18 error: abs: operand is undefined |
| %4:f32 = abs undef |
| ^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_OutOfScopeArg) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* i = b.Var<function, f32>("i"); |
| i->SetInitializer(b.Constant(0_f)); |
| auto* load = b.Load(i); |
| auto* c = b.Call(ty.f32(), BuiltinFn::kAbs, load->Result()); |
| auto* new_load = b.Load(i); |
| c->SetOperands(Vector{new_load->Result()}); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr(R"(:5:18 error: abs: %5 is not in scope |
| %4:f32 = abs %5 |
| ^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_TooFewResults) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* i = b.Var<function, f32>("i"); |
| i->SetInitializer(b.Constant(0_f)); |
| auto* load = b.Load(i); |
| auto* too_few_call = b.Call(ty.f32(), BuiltinFn::kAbs, load->Result()); |
| too_few_call->ClearResults(); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:5:13 error: abs: expected exactly 1 results, got 0 |
| undef = abs %3 |
| ^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_TooManyResults) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* i = b.Var<function, f32>("i"); |
| i->SetInitializer(b.Constant(0_f)); |
| auto* load = b.Load(i); |
| auto* too_many_call = b.Call(ty.f32(), BuiltinFn::kAbs, load->Result()); |
| too_many_call->SetResults(Vector{b.InstructionResult<f32>(), b.InstructionResult<f32>()}); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:5:22 error: abs: expected exactly 1 results, got 2 |
| %4:f32, %5:f32 = abs %3 |
| ^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_TooFewArgs) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* i = b.Var<function, f32>("i"); |
| i->SetInitializer(b.Constant(0_f)); |
| auto* load = b.Load(i); |
| auto* too_few_call = b.Call(ty.f32(), BuiltinFn::kAbs, load->Result()); |
| too_few_call->ClearOperands(); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:5:14 error: abs: no matching call to 'abs()')")) |
| << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallToBuiltin_TooManyArgs) { |
| auto* f = b.Function("f", ty.void_()); |
| b.Append(f->Block(), [&] { |
| auto* i = b.Var<function, f32>("i"); |
| i->SetInitializer(b.Constant(0_f)); |
| auto* load = b.Load(i); |
| auto* too_many_call = b.Call(ty.f32(), BuiltinFn::kAbs, load->Result()); |
| too_many_call->SetOperands(Vector{load->Result(), load->Result()}); |
| b.Return(f); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr(R"(:5:14 error: abs: no matching call to 'abs(f32, f32)')")) |
| << res.Failure(); |
| } |
| |
| // Test that a user declared structure cannot be used as the result type for a frexp builtin, even |
| // if it has the correct name and shape. |
| // See crbug.com/396344373. |
| TEST_F(IR_ValidatorTest, CallToBuiltin_Frexp_UserDeclaredResultStruct) { |
| auto* str_ty = |
| ty.Struct(mod.symbols.New("__frexp_result_f32"), { |
| {mod.symbols.New("fract"), ty.f32()}, |
| {mod.symbols.New("exp"), ty.i32()}, |
| }); |
| |
| auto* f = b.Function("f", ty.f32()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(str_ty, BuiltinFn::kFrexp, 1_f); |
| b.Return(f, b.Access<f32>(c, 0_u)); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT( |
| res.Failure().reason, |
| testing::HasSubstr( |
| R"(error: frexp: call result type '__frexp_result_f32' does not match builtin return type '__frexp_result_f32' |
| %2:__frexp_result_f32 = frexp 1.0f |
| ^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| // Test that a user declared structure cannot be used as the result type for a modf builtin, even |
| // if it has the correct name and shape. |
| // See crbug.com/396344373. |
| TEST_F(IR_ValidatorTest, CallToBuiltin_Modf_UserDeclaredResultStruct) { |
| auto* str_ty = ty.Struct(mod.symbols.New("__modf_result_vec4_f32"), |
| { |
| {mod.symbols.New("fract"), ty.vec4<f32>()}, |
| {mod.symbols.New("whole"), ty.vec4<f32>()}, |
| }); |
| |
| auto* f = b.Function("f", ty.vec4<f32>()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(str_ty, BuiltinFn::kModf, b.Splat<vec4<f32>>(1_f)); |
| b.Return(f, b.Access<vec4<f32>>(c, 0_u)); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT( |
| res.Failure().reason, |
| testing::HasSubstr( |
| R"(error: modf: call result type '__modf_result_vec4_f32' does not match builtin return type '__modf_result_vec4_f32' |
| %2:__modf_result_vec4_f32 = modf vec4<f32>(1.0f) |
| ^^^^ |
| )")) << res.Failure(); |
| } |
| |
| // Test that a user declared structure cannot be used as the result type for an |
| // atomicCompareExchangeWeak builtin, even if it has the correct name and shape. |
| // See crbug.com/396344373. |
| TEST_F(IR_ValidatorTest, CallToBuiltin_AtomicCompareExchange_UserDeclaredResultStruct) { |
| auto* str_ty = ty.Struct(mod.symbols.New("__atomic_compare_exchange_result_u32"), |
| { |
| {mod.symbols.New("old_value"), ty.u32()}, |
| {mod.symbols.New("exchanged"), ty.bool_()}, |
| }); |
| |
| auto* a = b.Var("a", ty.ptr<workgroup, atomic<u32>>()); |
| mod.root_block->Append(a); |
| |
| auto* f = b.Function("f", ty.u32()); |
| b.Append(f->Block(), [&] { |
| auto* c = b.Call(str_ty, BuiltinFn::kAtomicCompareExchangeWeak, a, 0_u, 1_u); |
| b.Return(f, b.Access<u32>(c, 0_u)); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT( |
| res.Failure().reason, |
| testing::HasSubstr( |
| R"(error: atomicCompareExchangeWeak: call result type '__atomic_compare_exchange_result_u32' does not match builtin return type '__atomic_compare_exchange_result_u32' |
| %3:__atomic_compare_exchange_result_u32 = atomicCompareExchangeWeak %a, 0u, 1u |
| ^^^^^^^^^^^^^^^^^^^^^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallBuiltinFn_Component_TooSmall) { |
| core::ir::Var* tex = nullptr; |
| core::ir::Var* sampler = nullptr; |
| b.Append(b.ir.root_block, [&] { |
| tex = |
| b.Var(ty.ptr(handle, ty.sampled_texture(core::type::TextureDimension::k2d, ty.i32()))); |
| tex->SetBindingPoint(0, 0); |
| |
| sampler = b.Var(ty.ptr(handle, ty.sampler())); |
| sampler->SetBindingPoint(0, 1); |
| }); |
| |
| auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| b.Append(func->Block(), [&] { |
| auto* coords = b.Construct(ty.vec2<f32>(), b.Value(1_f), b.Value(2_f)); |
| auto* offset = b.Composite<vec2<i32>>(1_i, 3_i); |
| |
| auto* t = b.Load(tex); |
| auto* s = b.Load(sampler); |
| b.Let("x", b.Call<vec4<i32>>(core::BuiltinFn::kTextureGather, -2_i, t, s, coords, offset)); |
| b.Return(func); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr( |
| R"(:11:34 error: textureGather: -2 outside range of [0, 3] |
| %7:vec4<i32> = textureGather -2i, %5, %6, %4, vec2<i32>(1i, 3i) |
| ^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallBuiltinFn_Component_TooBig) { |
| core::ir::Var* tex = nullptr; |
| core::ir::Var* sampler = nullptr; |
| b.Append(b.ir.root_block, [&] { |
| tex = |
| b.Var(ty.ptr(handle, ty.sampled_texture(core::type::TextureDimension::k2d, ty.i32()))); |
| tex->SetBindingPoint(0, 0); |
| |
| sampler = b.Var(ty.ptr(handle, ty.sampler())); |
| sampler->SetBindingPoint(0, 1); |
| }); |
| |
| auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| b.Append(func->Block(), [&] { |
| auto* coords = b.Construct(ty.vec2<f32>(), b.Value(1_f), b.Value(2_f)); |
| auto* offset = b.Composite<vec2<i32>>(1_i, 3_i); |
| |
| auto* t = b.Load(tex); |
| auto* s = b.Load(sampler); |
| b.Let("x", b.Call<vec4<i32>>(core::BuiltinFn::kTextureGather, 6_u, t, s, coords, offset)); |
| b.Return(func); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr( |
| R"(:11:34 error: textureGather: 6 outside range of [0, 3] |
| %7:vec4<i32> = textureGather 6u, %5, %6, %4, vec2<i32>(1i, 3i) |
| ^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallBuiltinFn_Offset_TooSmall) { |
| core::ir::Var* tex = nullptr; |
| core::ir::Var* sampler = nullptr; |
| b.Append(b.ir.root_block, [&] { |
| tex = b.Var( |
| ty.ptr(handle, ty.sampled_texture(core::type::TextureDimension::k2dArray, ty.i32()))); |
| tex->SetBindingPoint(0, 0); |
| |
| sampler = b.Var(ty.ptr(handle, ty.sampler())); |
| sampler->SetBindingPoint(0, 1); |
| }); |
| |
| auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| b.Append(func->Block(), [&] { |
| auto* coords = b.Construct(ty.vec2<f32>(), b.Value(1_f), b.Value(2_f)); |
| auto* array_idx = b.Value(1_i); |
| auto* offset = b.Composite<vec2<i32>>(1_i, -9_i); |
| |
| auto* t = b.Load(tex); |
| auto* s = b.Load(sampler); |
| b.Let("x", b.Call<vec4<i32>>(core::BuiltinFn::kTextureGather, 2_u, t, s, coords, array_idx, |
| offset)); |
| b.Return(func); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr( |
| R"(:11:54 error: textureGather: -9 outside range of [-8, 7] |
| %7:vec4<i32> = textureGather 2u, %5, %6, %4, 1i, vec2<i32>(1i, -9i) |
| ^^^^^^^^^^^^^^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallBuiltinFn_Offset_TooBig) { |
| core::ir::Var* tex = nullptr; |
| core::ir::Var* sampler = nullptr; |
| b.Append(b.ir.root_block, [&] { |
| tex = b.Var( |
| ty.ptr(handle, ty.sampled_texture(core::type::TextureDimension::k2dArray, ty.i32()))); |
| tex->SetBindingPoint(0, 0); |
| |
| sampler = b.Var(ty.ptr(handle, ty.sampler())); |
| sampler->SetBindingPoint(0, 1); |
| }); |
| |
| auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| b.Append(func->Block(), [&] { |
| auto* coords = b.Construct(ty.vec2<f32>(), b.Value(1_f), b.Value(2_f)); |
| auto* array_idx = b.Value(1_i); |
| auto* offset = b.Composite<vec2<i32>>(8_i, -2_i); |
| |
| auto* t = b.Load(tex); |
| auto* s = b.Load(sampler); |
| b.Let("x", b.Call<vec4<i32>>(core::BuiltinFn::kTextureGather, 2_u, t, s, coords, array_idx, |
| offset)); |
| b.Return(func); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr( |
| R"(:11:54 error: textureGather: 8 outside range of [-8, 7] |
| %7:vec4<i32> = textureGather 2u, %5, %6, %4, 1i, vec2<i32>(8i, -2i) |
| ^^^^^^^^^^^^^^^^^^ |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallBuiltinFn_QuadBroadcast_NonConstId) { |
| auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| b.Append(func->Block(), [&] { |
| auto* id = b.Let("a", 2_u); |
| b.Let("x", b.Call<i32>(core::BuiltinFn::kQuadBroadcast, 2_i, id)); |
| b.Return(func); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, testing::HasSubstr( |
| R"(:4:32 error: quadBroadcast: non-constant ID provided |
| %3:i32 = quadBroadcast 2i, %a |
| ^^ |
| |
| )")) << res.Failure(); |
| } |
| |
| TEST_F(IR_ValidatorTest, CallBuiltinFn_SubgroupBroadcast_NonConstId) { |
| auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment); |
| b.Append(func->Block(), [&] { |
| auto* id = b.Let("a", 2_u); |
| b.Let("x", b.Call<i32>(core::BuiltinFn::kSubgroupBroadcast, 2_i, id)); |
| b.Return(func); |
| }); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_NE(res, Success); |
| EXPECT_THAT(res.Failure().reason, |
| testing::HasSubstr( |
| R"(:4:36 error: subgroupBroadcast: non-constant ID provided |
| %3:i32 = subgroupBroadcast 2i, %a |
| ^^ |
| |
| )")) << res.Failure(); |
| } |
| |
| } // namespace tint::core::ir |