[ir][validation] Validate `var`
Add validation for the `var` instruction. This CL checks that the
`result` for the `var` is not `nullptr`.
Bug: 1952
Change-Id: Ib68694c83f53c0315cb4b5861b759748df1aa500
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/138221
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index 8db4098..8028d02 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -76,14 +76,6 @@
using DisableIfVectorLike = utils::traits::EnableIf<
!utils::IsVectorLike<utils::traits::Decay<utils::traits::NthTypeOf<0, TYPES..., void>>>>;
- template <typename T>
- T* Append(T* val) {
- if (current_block_) {
- current_block_->Append(val);
- }
- return val;
- }
-
/// If set, any created instruction will be auto-appended to the block.
ir::Block* current_block_ = nullptr;
@@ -103,6 +95,18 @@
/// @returns the builder
Builder With(Block* b) { return Builder(ir, b); }
+ /// Appends and returns the instruction @p val to the current block. If there is no current
+ /// block bound, then @p val is just returned.
+ /// @param val the instruction to append
+ /// @returns the instruction
+ template <typename T>
+ T* Append(T* val) {
+ if (current_block_) {
+ current_block_->Append(val);
+ }
+ return val;
+ }
+
/// @returns a new block
ir::Block* Block();
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 7ff5cf5..e23f88e 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -295,7 +295,11 @@
}
void Disassembler::EmitValueWithType(Instruction* val) {
- EmitValueWithType(val->Result());
+ if (val->Result()) {
+ EmitValueWithType(val->Result());
+ } else {
+ out_ << "undef";
+ }
}
void Disassembler::EmitValueWithType(Value* val) {
diff --git a/src/tint/ir/validate.cc b/src/tint/ir/validate.cc
index 34436e3..3f82487 100644
--- a/src/tint/ir/validate.cc
+++ b/src/tint/ir/validate.cc
@@ -158,6 +158,7 @@
std::string("root block: invalid instruction: ") + inst->TypeInfo().name);
continue;
}
+ CheckVar(var);
}
}
@@ -181,6 +182,25 @@
}
void CheckInstruction(Instruction* inst) {
+ if (!inst->Alive()) {
+ AddError(inst, "destroyed instruction found in instruction list");
+ }
+ if (inst->Result()) {
+ if (inst->Result()->Source() == nullptr) {
+ AddError(inst, "instruction result source is undefined");
+ } else if (inst->Result()->Source() != inst) {
+ AddError(inst, "instruction result source has wrong instruction");
+ }
+ }
+
+ for (auto* op : inst->Operands()) {
+ // Note, a `nullptr` is a valid operand in some cases, like `var` so we can't just check
+ // for `nullptr` here.
+ if (op && !op->Alive()) {
+ AddError(inst, "instruction has undefined operand");
+ }
+ }
+
tint::Switch(
inst, //
[&](Access* a) { CheckAccess(a); }, //
@@ -194,7 +214,7 @@
[&](Swizzle*) {}, //
[&](Terminator* b) { CheckTerminator(b); }, //
[&](Unary*) {}, //
- [&](Var*) {}, //
+ [&](Var* var) { CheckVar(var); }, //
[&](Default) {
AddError(std::string("missing validation of: ") + inst->TypeInfo().name);
});
@@ -310,6 +330,18 @@
AddError(if_, If::kConditionOperandOffset, "if: condition must be a `bool` type");
}
}
+
+ void CheckVar(Var* var) {
+ if (var->Result() == nullptr) {
+ AddError(var, "var: result is a nullptr");
+ }
+
+ if (var->Result() && var->Initializer()) {
+ if (var->Initializer()->Type() != var->Result()->Type()->UnwrapPtr()) {
+ AddError(var, "var initializer has incorrect type");
+ }
+ }
+ }
}; // namespace
} // namespace
diff --git a/src/tint/ir/validate_test.cc b/src/tint/ir/validate_test.cc
index d49f553..2569415 100644
--- a/src/tint/ir/validate_test.cc
+++ b/src/tint/ir/validate_test.cc
@@ -528,5 +528,189 @@
)");
}
+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