[tint][ir][val] Check results and operands for bitcasts

Checking for sizes matching is deferred to crbug.com/359253674

- Also adds in a couple test cases for CheckConvert, since they follow
  the same pattern

Fixes: 358393348
Change-Id: I8aa593157b9b8ef69c344142a7c20105d0a6257f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/201974
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/bitcast.h b/src/tint/lang/core/ir/bitcast.h
index fa28388..a6f5cd1 100644
--- a/src/tint/lang/core/ir/bitcast.h
+++ b/src/tint/lang/core/ir/bitcast.h
@@ -41,6 +41,12 @@
     /// The offset in Operands() for the value
     static constexpr size_t kValueOperandOffset = 0;
 
+    /// The fixed number of results returned by this instruction
+    static constexpr size_t kNumResults = 1;
+
+    /// The fixed number of operands expected for this instruction
+    static constexpr size_t kNumOperands = 1;
+
     /// Constructor (no results, no operands)
     /// @param id the instruction id
     explicit Bitcast(Id id);
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 4bb4bd2..44f9ed9 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -365,6 +365,10 @@
     /// @param call the call to validate
     void CheckCall(const Call* call);
 
+    /// Validates the given bitcast
+    /// @param bitcast the bitcast to validate
+    void CheckBitcast(const Bitcast* bitcast);
+
     /// Validates the given builtin call
     /// @param call the call to validate
     void CheckBuiltinCall(const BuiltinCall* call);
@@ -1284,7 +1288,7 @@
 void Validator::CheckCall(const Call* call) {
     tint::Switch(
         call,                                                            //
-        [&](const Bitcast*) {},                                          //
+        [&](const Bitcast* b) { CheckBitcast(b); },                      //
         [&](const BuiltinCall* c) { CheckBuiltinCall(c); },              //
         [&](const MemberBuiltinCall* c) { CheckMemberBuiltinCall(c); },  //
         [&](const Construct* c) { CheckConstruct(c); },                  //
@@ -1296,6 +1300,10 @@
         });
 }
 
+void Validator::CheckBitcast(const Bitcast* bitcast) {
+    CheckResultsAndOperands(bitcast, Bitcast::kNumResults, Bitcast::kNumOperands);
+}
+
 void Validator::CheckBuiltinCall(const BuiltinCall* call) {
     auto args =
         Transform<8>(call->Args(), [&](const ir::Value* v) { return v ? v->Type() : nullptr; });
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 6a36917..05d9681 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -922,6 +922,129 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Bitcast_MissingArg) {
+    auto* f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* bitcast = b.Bitcast(ty.i32(), 1_u);
+        bitcast->ClearOperands();
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:14 error: bitcast: expected exactly 1 operands, got 0
+    %2:i32 = bitcast
+             ^^^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+note: # Disassembly
+%f = func():void {
+  $B1: {
+    %2:i32 = bitcast
+    ret
+  }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Bitcast_NullArg) {
+    auto* f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] {
+        b.Bitcast(ty.i32(), nullptr);
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:22 error: bitcast: operand is undefined
+    %2:i32 = bitcast undef
+                     ^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+note: # Disassembly
+%f = func():void {
+  $B1: {
+    %2:i32 = bitcast undef
+    ret
+  }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Bitcast_MissingResult) {
+    auto* f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* bitcast = b.Bitcast(ty.i32(), 1_u);
+        bitcast->ClearResults();
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:13 error: bitcast: expected exactly 1 results, got 0
+    undef = bitcast 1u
+            ^^^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+note: # Disassembly
+%f = func():void {
+  $B1: {
+    undef = bitcast 1u
+    ret
+  }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Bitcast_NullResult) {
+    auto* f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* c = b.Bitcast(ty.i32(), 1_u);
+        c->SetResults(Vector<InstructionResult*, 1>{nullptr});
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:5 error: bitcast: result is undefined
+    undef = bitcast 1u
+    ^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+:3:5 error: bitcast: result is undefined
+    undef = bitcast 1u
+    ^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+note: # Disassembly
+%f = func():void {
+  $B1: {
+    undef = bitcast 1u
+    ret
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Construct_Struct_ZeroValue) {
     auto* str_ty = ty.Struct(mod.symbols.New("MyStruct"), {
                                                               {mod.symbols.New("a"), ty.i32()},
@@ -1209,10 +1332,39 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Convert_MissingArg) {
+    auto* f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* c = b.Convert(ty.i32(), 1_f);
+        c->ClearOperands();
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:14 error: convert: expected exactly 1 operands, got 0
+    %2:i32 = convert
+             ^^^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+note: # Disassembly
+%f = func():void {
+  $B1: {
+    %2:i32 = convert
+    ret
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Convert_NullArg) {
     auto* f = b.Function("f", ty.void_());
     b.Append(f->Block(), [&] {
-        b.Convert(ty.f32(), nullptr);
+        b.Convert(ty.i32(), nullptr);
         b.Return(f);
     });
 
@@ -1220,7 +1372,7 @@
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
               R"(:3:22 error: convert: operand is undefined
-    %2:f32 = convert undef
+    %2:i32 = convert undef
                      ^^^^^
 
 :2:3 note: in block
@@ -1230,7 +1382,36 @@
 note: # Disassembly
 %f = func():void {
   $B1: {
-    %2:f32 = convert undef
+    %2:i32 = convert undef
+    ret
+  }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Convert_MissingResult) {
+    auto* f = b.Function("f", ty.void_());
+    b.Append(f->Block(), [&] {
+        auto* c = b.Convert(ty.i32(), 1_f);
+        c->ClearResults();
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:13 error: convert: expected exactly 1 results, got 0
+    undef = convert 1.0f
+            ^^^^^^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+note: # Disassembly
+%f = func():void {
+  $B1: {
+    undef = convert 1.0f
     ret
   }
 }