[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