| // Copyright 2023 The Tint Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <utility> |
| |
| #include "gmock/gmock.h" |
| #include "src/tint/ir/builder.h" |
| #include "src/tint/ir/ir_test_helper.h" |
| #include "src/tint/ir/validate.h" |
| #include "src/tint/type/matrix.h" |
| #include "src/tint/type/pointer.h" |
| #include "src/tint/type/struct.h" |
| |
| namespace tint::ir { |
| namespace { |
| |
| using namespace tint::builtin::fluent_types; // NOLINT |
| using namespace tint::number_suffixes; // NOLINT |
| |
| using IR_ValidateTest = IRTestHelper; |
| |
| TEST_F(IR_ValidateTest, RootBlock_Var) { |
| mod.root_block = b.RootBlock(); |
| mod.root_block->Append(b.Var(ty.ptr<private_, i32>())); |
| auto res = ir::Validate(mod); |
| EXPECT_TRUE(res) << res.Failure().str(); |
| } |
| |
| TEST_F(IR_ValidateTest, RootBlock_NonVar) { |
| auto* l = b.Loop(); |
| l->Body()->Append(b.Continue(l)); |
| |
| mod.root_block = b.RootBlock(); |
| mod.root_block->Append(l); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:2:3 error: root block: invalid instruction: tint::ir::Loop |
| loop [b: %b2] { # loop_1 |
| ^^^^^^^^^^^^^ |
| |
| :1:1 note: In block |
| %b1 = block { # root |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %b1 = block { # root |
| loop [b: %b2] { # loop_1 |
| %b2 = block { # body |
| continue %b3 |
| } |
| } |
| } |
| |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Function) { |
| auto* f = b.Function("my_func", ty.void_()); |
| 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); |
| EXPECT_TRUE(res) << res.Failure().str(); |
| } |
| |
| TEST_F(IR_ValidateTest, Block_NoTerminator) { |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:2:3 error: block: does not end in a terminator instruction |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Valid_Access_Value) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.mat3x2<f32>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.f32(), obj, 1_u, 0_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| EXPECT_TRUE(res) << res.Failure().str(); |
| } |
| |
| TEST_F(IR_ValidateTest, Valid_Access_Ptr) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2<f32>>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.ptr<private_, f32>(), obj, 1_u, 0_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| EXPECT_TRUE(res) << res.Failure().str(); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_NegativeIndex) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.vec3<f32>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.f32(), obj, -1_i); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:25 error: access: constant index must be positive, got -1 |
| %3:f32 = access %2, -1i |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:vec3<f32>):void -> %b1 { |
| %b1 = block { |
| %3:f32 = access %2, -1i |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_OOB_Index_Value) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.mat3x2<f32>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.f32(), obj, 1_u, 3_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:29 error: access: index out of bounds for type vec2<f32> |
| %3:f32 = access %2, 1u, 3u |
| ^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| :3:29 note: acceptable range: [0..1] |
| %3:f32 = access %2, 1u, 3u |
| ^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:mat3x2<f32>):void -> %b1 { |
| %b1 = block { |
| %3:f32 = access %2, 1u, 3u |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_OOB_Index_Ptr) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2<f32>>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.ptr<private_, f32>(), obj, 1_u, 3_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), |
| R"(:3:55 error: access: index out of bounds for type ptr<vec2<f32>> |
| %3:ptr<private, f32, read_write> = access %2, 1u, 3u |
| ^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| :3:55 note: acceptable range: [0..1] |
| %3:ptr<private, f32, read_write> = access %2, 1u, 3u |
| ^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:ptr<private, mat3x2<f32>, read_write>):void -> %b1 { |
| %b1 = block { |
| %3:ptr<private, f32, read_write> = access %2, 1u, 3u |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_StaticallyUnindexableType_Value) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.f32()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.f32(), obj, 1_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:25 error: access: type f32 cannot be indexed |
| %3:f32 = access %2, 1u |
| ^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:f32):void -> %b1 { |
| %b1 = block { |
| %3:f32 = access %2, 1u |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_StaticallyUnindexableType_Ptr) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.ptr<private_, f32>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.ptr<private_, f32>(), obj, 1_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:51 error: access: type ptr<f32> cannot be indexed |
| %3:ptr<private, f32, read_write> = access %2, 1u |
| ^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:ptr<private, f32, read_write>):void -> %b1 { |
| %b1 = block { |
| %3:ptr<private, f32, read_write> = access %2, 1u |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_DynamicallyUnindexableType_Value) { |
| auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), { |
| {mod.symbols.New("a"), ty.i32()}, |
| {mod.symbols.New("b"), ty.i32()}, |
| }); |
| |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(str_ty); |
| auto* idx = b.FunctionParam(ty.i32()); |
| f->SetParams({obj, idx}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.i32(), obj, idx); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), |
| R"(:8:25 error: access: type MyStruct cannot be dynamically indexed |
| %4:i32 = access %2, %3 |
| ^^ |
| |
| :7:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| MyStruct = struct @align(4) { |
| a:i32 @offset(0) |
| b:i32 @offset(4) |
| } |
| |
| %my_func = func(%2:MyStruct, %3:i32):void -> %b1 { |
| %b1 = block { |
| %4:i32 = access %2, %3 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_DynamicallyUnindexableType_Ptr) { |
| auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), { |
| {mod.symbols.New("a"), ty.i32()}, |
| {mod.symbols.New("b"), ty.i32()}, |
| }); |
| |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.ptr<private_, read_write>(str_ty)); |
| auto* idx = b.FunctionParam(ty.i32()); |
| f->SetParams({obj, idx}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.i32(), obj, idx); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), |
| R"(:8:25 error: access: type ptr<MyStruct> cannot be dynamically indexed |
| %4:i32 = access %2, %3 |
| ^^ |
| |
| :7:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| MyStruct = struct @align(4) { |
| a:i32 @offset(0) |
| b:i32 @offset(4) |
| } |
| |
| %my_func = func(%2:ptr<private, MyStruct, read_write>, %3:i32):void -> %b1 { |
| %b1 = block { |
| %4:i32 = access %2, %3 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_Incorrect_Type_Value_Value) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.mat3x2<f32>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.i32(), obj, 1_u, 1_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), |
| R"(:3:14 error: access: result of access chain is type f32 but instruction type is i32 |
| %3:i32 = access %2, 1u, 1u |
| ^^^^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:mat3x2<f32>):void -> %b1 { |
| %b1 = block { |
| %3:i32 = access %2, 1u, 1u |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_Incorrect_Type_Ptr_Ptr) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2<f32>>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.ptr<private_, i32>(), obj, 1_u, 1_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ( |
| res.Failure().str(), |
| R"(:3:40 error: access: result of access chain is type ptr<f32> but instruction type is ptr<i32> |
| %3:ptr<private, i32, read_write> = access %2, 1u, 1u |
| ^^^^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:ptr<private, mat3x2<f32>, read_write>):void -> %b1 { |
| %b1 = block { |
| %3:ptr<private, i32, read_write> = access %2, 1u, 1u |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Access_Incorrect_Type_Ptr_Value) { |
| auto* f = b.Function("my_func", ty.void_()); |
| auto* obj = b.FunctionParam(ty.ptr<private_, mat3x2<f32>>()); |
| f->SetParams({obj}); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Access(ty.f32(), obj, 1_u, 1_u); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ( |
| res.Failure().str(), |
| R"(:3:14 error: access: result of access chain is type ptr<f32> but instruction type is f32 |
| %3:f32 = access %2, 1u, 1u |
| ^^^^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func(%2:ptr<private, mat3x2<f32>, read_write>):void -> %b1 { |
| %b1 = block { |
| %3:f32 = access %2, 1u, 1u |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Block_TerminatorInMiddle) { |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->Block()); |
| sb.Return(f); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), |
| R"(:3:5 error: block: terminator which isn't the final instruction |
| ret |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| ret |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, If_ConditionIsBool) { |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto* if_ = b.If(1_i); |
| if_->True()->Append(b.Return(f)); |
| if_->False()->Append(b.Return(f)); |
| |
| f->Block()->Append(if_); |
| f->Block()->Append(b.Return(f)); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:8 error: if: condition must be a `bool` type |
| if 1i [t: %b2, f: %b3] { # if_1 |
| ^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| if 1i [t: %b2, f: %b3] { # if_1 |
| %b2 = block { # true |
| ret |
| } |
| %b3 = block { # false |
| ret |
| } |
| } |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Var_RootBlock_NullResult) { |
| auto* v = mod.instructions.Create<ir::Var>(nullptr); |
| b.RootBlock()->Append(v); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:2:11 error: var: result is a nullptr |
| undef = var |
| ^^^ |
| |
| :1:1 note: In block |
| %b1 = block { # root |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %b1 = block { # root |
| undef = var |
| } |
| |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Var_Function_NullResult) { |
| auto* v = mod.instructions.Create<ir::Var>(nullptr); |
| |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->StartTarget()); |
| sb.Append(v); |
| sb.Return(f); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:13 error: var: result is a nullptr |
| undef = var |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| undef = var |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Var_Init_WrongType) { |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->StartTarget()); |
| auto* v = sb.Var(ty.ptr<function, f32>()); |
| sb.Return(f); |
| |
| auto* result = sb.InstructionResult(ty.i32()); |
| v->SetInitializer(result); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:41 error: var initializer has incorrect type |
| %2:ptr<function, f32, read_write> = var, %3 |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| %2:ptr<function, f32, read_write> = var, %3 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Instruction_AppendedDead) { |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->StartTarget()); |
| auto* v = sb.Var(ty.ptr<function, f32>()); |
| auto* ret = sb.Return(f); |
| |
| v->Destroy(); |
| v->InsertBefore(ret); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:41 error: destroyed instruction found in instruction list |
| %2:ptr<function, f32, read_write> = var |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| :3:41 error: instruction result source is undefined |
| %2:ptr<function, f32, read_write> = var |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| %2:ptr<function, f32, read_write> = var |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Instruction_NullSource) { |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->StartTarget()); |
| auto* v = sb.Var(ty.ptr<function, f32>()); |
| sb.Return(f); |
| |
| v->Result()->SetSource(nullptr); |
| |
| auto res = ir::Validate(mod); |
| ASSERT_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:41 error: instruction result source is undefined |
| %2:ptr<function, f32, read_write> = var |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| %2:ptr<function, f32, read_write> = var |
| ret |
| } |
| } |
| )"); |
| } |
| |
| TEST_F(IR_ValidateTest, Instruction_DeadOperand) { |
| auto* f = b.Function("my_func", ty.void_()); |
| mod.functions.Push(f); |
| |
| auto sb = b.With(f->StartTarget()); |
| 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_FALSE(res); |
| EXPECT_EQ(res.Failure().str(), R"(:3:41 error: instruction has undefined operand |
| %2:ptr<function, f32, read_write> = var, %3 |
| ^^^ |
| |
| :2:3 note: In block |
| %b1 = block { |
| ^^^^^^^^^^^ |
| |
| note: # Disassembly |
| %my_func = func():void -> %b1 { |
| %b1 = block { |
| %2:ptr<function, f32, read_write> = var, %3 |
| ret |
| } |
| } |
| )"); |
| } |
| |
| } // namespace |
| } // namespace tint::ir |