[tint][ir][val] Improve checks for variable initializers

- Makes sure that initializers are only used for private or function
  address space variables
- Catches an annoying case where an ir::Function has been supplied as
  a initializer

Fixes: 381219432
Change-Id: I12b9fc9aae7323d7d8726acbe5800e02d25eb9dd
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/217014
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 70fc255..098aaee 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -2350,21 +2350,6 @@
         return;
     }
 
-    // Check that initializer and result type match
-    if (var->Initializer()) {
-        if (!CheckOperand(var, ir::Var::kInitializerOperandOffset)) {
-            return;
-        }
-
-        if (var->Initializer()->Type() != var->Result(0)->Type()->UnwrapPtrOrRef()) {
-            AddError(var) << "initializer type "
-                          << style::Type(var->Initializer()->Type()->FriendlyName())
-                          << " does not match store type "
-                          << style::Type(var->Result(0)->Type()->UnwrapPtrOrRef()->FriendlyName());
-            return;
-        }
-    }
-
     auto* result_type = var->Result(0)->Type();
     if (result_type == nullptr) {
         AddError(var) << "result type is undefined";
@@ -2385,6 +2370,42 @@
         }
     }
 
+    // Check that initializer and result type match
+    if (var->Initializer()) {
+        if (mv->AddressSpace() != AddressSpace::kFunction &&
+            mv->AddressSpace() != AddressSpace::kPrivate) {
+            AddError(var)
+                << "only variables in the function or private address space may be initialized";
+            return;
+        }
+
+        if (!CheckOperand(var, ir::Var::kInitializerOperandOffset)) {
+            return;
+        }
+
+        if (var->Operand(ir::Var::kInitializerOperandOffset)->Is<ir::Function>()) {
+            // ir::Function has a null ->Type(), and won't be filtered by CheckOperand, so needs
+            // special handling here
+            AddError(var) << "initializer type 'function' does not match store type "
+                          << style::Type(var->Result(0)->Type()->UnwrapPtrOrRef()->FriendlyName());
+            return;
+        }
+
+        if (var->Initializer()->Type() != var->Result(0)->Type()->UnwrapPtrOrRef()) {
+            AddError(var) << "initializer type "
+                          << style::Type(var->Initializer()->Type()->FriendlyName())
+                          << " does not match store type "
+                          << style::Type(var->Result(0)->Type()->UnwrapPtrOrRef()->FriendlyName());
+            return;
+        }
+    }
+
+    if (auto result = ValidateBindingPoint(var->BindingPoint(), mv->AddressSpace());
+        result != Success) {
+        AddError(var) << result.Failure();
+        return;
+    }
+
     if (var->Block() == mod_.root_block && mv->AddressSpace() == AddressSpace::kFunction) {
         AddError(var) << "vars in the 'function' address space must be in a function scope";
         return;
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index a9dfb11..b5be47b 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -5411,6 +5411,85 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Var_Init_FunctionTypeInit) {
+    auto* invalid = b.Function("invalid_init", ty.void_());
+    b.Append(invalid->Block(), [&] { b.Return(invalid); });
+    auto* f = b.Function("my_func", ty.void_());
+
+    b.Append(f->Block(), [&] {
+        auto* i = b.Var<function, f32>("i");
+        i->SetInitializer(invalid);
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:8:41 error: var: initializer type 'function' does not match store type 'f32'
+    %i:ptr<function, f32, read_write> = var, %invalid_init
+                                        ^^^
+
+:7:3 note: in block
+  $B2: {
+  ^^^
+
+note: # Disassembly
+%invalid_init = func():void {
+  $B1: {
+    ret
+  }
+}
+%my_func = func():void {
+  $B2: {
+    %i:ptr<function, f32, read_write> = var, %invalid_init
+    ret
+  }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Var_Init_InvalidAddressSpace) {
+    auto* p = b.Var<private_, f32>("p");
+    p->SetInitializer(b.Constant(1_f));
+    mod.root_block->Append(p);
+    auto* s = b.Var<storage, f32>("s");
+    s->SetInitializer(b.Constant(1_f));
+    mod.root_block->Append(s);
+    auto* f = b.Function("my_func", ty.void_());
+
+    b.Append(f->Block(), [&] {
+        auto* v = b.Var<function, f32>("v");
+        v->SetInitializer(b.Constant(1_f));
+        b.Return(f);
+    });
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(
+        res.Failure().reason.Str(),
+        R"(:3:38 error: var: only variables in the function or private address space may be initialized
+  %s:ptr<storage, f32, read_write> = var, 1.0f
+                                     ^^^
+
+:1:1 note: in block
+$B1: {  # root
+^^^
+
+note: # Disassembly
+$B1: {  # root
+  %p:ptr<private, f32, read_write> = var, 1.0f
+  %s:ptr<storage, f32, read_write> = var, 1.0f
+}
+
+%my_func = func():void {
+  $B2: {
+    %v:ptr<function, f32, read_write> = var, 1.0f
+    ret
+  }
+}
+)");
+}
+
 TEST_F(IR_ValidatorTest, Var_HandleMissingBindingPoint) {
     auto* v = b.Var(ty.ptr<handle, i32>());
     mod.root_block->Append(v);