[ir][validation] Validate `binary`
Add validation for the `binary` instruction. This CL checks that the
`result`, `lhs` and `rhs` for the `binary` is not `undef`.
Bug: 1952
Change-Id: Ied005c3ac750e2d4437fa7fa197acdde0b8bb1a8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/138222
Reviewed-by: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index f1e93a1..03c88ee 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -378,7 +378,7 @@
[&](If* i) { EmitIf(i); }, //
[&](Loop* l) { EmitLoop(l); }, //
[&](Binary* b) { EmitBinary(b); }, //
- [&](Unary* u) { EmitUnary(u); },
+ [&](Unary* u) { EmitUnary(u); }, //
[&](Bitcast* b) {
EmitValueWithType(b);
out_ << " = ";
@@ -448,7 +448,7 @@
EmitInstructionName("var", v);
if (v->Initializer()) {
out_ << ", ";
- EmitValue(v->Initializer());
+ EmitOperand(v, v->Initializer(), Var::kInitializerOperandOffset);
}
if (v->BindingPoint().has_value()) {
out_ << " ";
@@ -685,6 +685,7 @@
}
void Disassembler::EmitBinary(Binary* b) {
+ SourceMarker sm(this);
EmitValueWithType(b);
out_ << " = ";
switch (b->Kind()) {
@@ -741,10 +742,13 @@
EmitValue(b->LHS());
out_ << ", ";
EmitValue(b->RHS());
+
+ sm.Store(b);
EmitLine();
}
void Disassembler::EmitUnary(Unary* u) {
+ SourceMarker sm(this);
EmitValueWithType(u);
out_ << " = ";
switch (u->Kind()) {
@@ -757,6 +761,8 @@
}
out_ << " ";
EmitValue(u->Val());
+
+ sm.Store(u);
EmitLine();
}
diff --git a/src/tint/ir/validate.cc b/src/tint/ir/validate.cc
index 3f82487..d8871b9 100644
--- a/src/tint/ir/validate.cc
+++ b/src/tint/ir/validate.cc
@@ -193,18 +193,28 @@
}
}
- for (auto* op : inst->Operands()) {
+ auto ops = inst->Operands();
+ for (size_t i = 0; i < ops.Length(); ++i) {
+ auto* op = ops[i];
+ if (!op) {
+ continue;
+ }
+
// 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()) {
+ if (!op->Alive()) {
AddError(inst, "instruction has undefined operand");
}
+
+ if (!op->Usages().Contains({inst, i})) {
+ AddError(inst, i, "instruction operand missing usage");
+ }
}
tint::Switch(
inst, //
[&](Access* a) { CheckAccess(a); }, //
- [&](Binary*) {}, //
+ [&](Binary* b) { CheckBinary(b); }, //
[&](Call* c) { CheckCall(c); }, //
[&](If* if_) { CheckIf(if_); }, //
[&](Load*) {}, //
@@ -302,6 +312,18 @@
}
}
+ void CheckBinary(ir::Binary* b) {
+ if (b->LHS() == nullptr) {
+ AddError(b, "binary: left operand is undefined");
+ }
+ if (b->RHS() == nullptr) {
+ AddError(b, "binary: right operand is undefined");
+ }
+ if (b->Result() == nullptr) {
+ AddError(b, "binary: result is undefined");
+ }
+ }
+
void CheckTerminator(ir::Terminator* b) {
tint::Switch(
b, //
@@ -324,7 +346,7 @@
void CheckIf(If* if_) {
if (!if_->Condition()) {
- AddError(if_, "if: condition is nullptr");
+ AddError(if_, "if: condition is undefined");
}
if (if_->Condition() && !if_->Condition()->Type()->Is<type::Bool>()) {
AddError(if_, If::kConditionOperandOffset, "if: condition must be a `bool` type");
@@ -333,7 +355,7 @@
void CheckVar(Var* var) {
if (var->Result() == nullptr) {
- AddError(var, "var: result is a nullptr");
+ AddError(var, "var: result is undefined");
}
if (var->Result() && var->Initializer()) {
diff --git a/src/tint/ir/validate_test.cc b/src/tint/ir/validate_test.cc
index 9908c45..8d696e1 100644
--- a/src/tint/ir/validate_test.cc
+++ b/src/tint/ir/validate_test.cc
@@ -547,7 +547,7 @@
auto res = ir::Validate(mod);
ASSERT_FALSE(res);
- EXPECT_EQ(res.Failure().str(), R"(:2:11 error: var: result is a nullptr
+ EXPECT_EQ(res.Failure().str(), R"(:2:11 error: var: result is undefined
undef = var
^^^
@@ -575,7 +575,7 @@
auto res = ir::Validate(mod);
ASSERT_FALSE(res);
- EXPECT_EQ(res.Failure().str(), R"(:3:13 error: var: result is a nullptr
+ EXPECT_EQ(res.Failure().str(), R"(:3:13 error: var: result is undefined
undef = var
^^^
@@ -725,5 +725,124 @@
)");
}
+TEST_F(IR_ValidateTest, Instruction_OperandUsageRemoved) {
+ auto* f = b.Function("my_func", ty.void_());
+ mod.functions.Push(f);
+
+ auto sb = b.With(f->Block());
+ auto* v = sb.Var(ty.ptr<function, f32>());
+ sb.Return(f);
+
+ auto* result = sb.InstructionResult(ty.f32());
+ v->SetInitializer(result);
+ result->RemoveUsage({v, 0u});
+
+ auto res = ir::Validate(mod);
+ ASSERT_FALSE(res);
+ EXPECT_EQ(res.Failure().str(), R"(:3:46 error: instruction operand missing usage
+ %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, Binary_LHS_Nullptr) {
+ auto* f = b.Function("my_func", ty.void_());
+ mod.functions.Push(f);
+
+ auto sb = b.With(f->Block());
+ sb.Add(ty.i32(), nullptr, sb.Constant(2_i));
+ sb.Return(f);
+
+ auto res = ir::Validate(mod);
+ ASSERT_FALSE(res);
+ EXPECT_EQ(res.Failure().str(), R"(:3:5 error: binary: left operand is undefined
+ %2:i32 = add undef, 2i
+ ^^^^^^^^^^^^^^^^^^^^^^
+
+:2:3 note: In block
+ %b1 = block {
+ ^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void -> %b1 {
+ %b1 = block {
+ %2:i32 = add undef, 2i
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidateTest, Binary_RHS_Nullptr) {
+ auto* f = b.Function("my_func", ty.void_());
+ mod.functions.Push(f);
+
+ auto sb = b.With(f->Block());
+ sb.Add(ty.i32(), sb.Constant(2_i), nullptr);
+ sb.Return(f);
+
+ auto res = ir::Validate(mod);
+ ASSERT_FALSE(res);
+ EXPECT_EQ(res.Failure().str(), R"(:3:5 error: binary: right operand is undefined
+ %2:i32 = add 2i, undef
+ ^^^^^^^^^^^^^^^^^^^^^^
+
+:2:3 note: In block
+ %b1 = block {
+ ^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void -> %b1 {
+ %b1 = block {
+ %2:i32 = add 2i, undef
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidateTest, Binary_Result_Nullptr) {
+ auto* bin = mod.instructions.Create<ir::Binary>(nullptr, ir::Binary::Kind::kAdd,
+ b.Constant(3_i), b.Constant(2_i));
+
+ auto* f = b.Function("my_func", ty.void_());
+ mod.functions.Push(f);
+
+ auto sb = b.With(f->Block());
+ sb.Append(bin);
+ sb.Return(f);
+
+ auto res = ir::Validate(mod);
+ ASSERT_FALSE(res);
+ EXPECT_EQ(res.Failure().str(), R"(:3:5 error: binary: result is undefined
+ undef = add 3i, 2i
+ ^^^^^^^^^^^^^^^^^^
+
+:2:3 note: In block
+ %b1 = block {
+ ^^^^^^^^^^^
+
+note: # Disassembly
+%my_func = func():void -> %b1 {
+ %b1 = block {
+ undef = add 3i, 2i
+ ret
+ }
+}
+)");
+}
+
} // namespace
} // namespace tint::ir