[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