[ir] Add a validator

This CL adds a simple validator for the IR.

Bug: tint: 1952
Change-Id: I16ef5475a4f7589a987dd166881dd17d4eec6f36
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/135100
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/BUILD.gn b/src/tint/BUILD.gn
index c90d217..afd91d6 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1279,6 +1279,8 @@
       "ir/unary.h",
       "ir/user_call.cc",
       "ir/user_call.h",
+      "ir/validate.cc",
+      "ir/validate.h",
       "ir/value.cc",
       "ir/value.h",
       "ir/var.cc",
@@ -2308,6 +2310,7 @@
         "ir/to_program_roundtrip_test.cc",
         "ir/transform/add_empty_entry_point_test.cc",
         "ir/unary_test.cc",
+        "ir/validate_test.cc",
       ]
 
       deps = [
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 186b8c6..cdcd22a 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -788,6 +788,8 @@
     ir/unary.h
     ir/user_call.cc
     ir/user_call.h
+    ir/validate.cc
+    ir/validate.h
     ir/value.cc
     ir/value.h
     ir/var.cc
@@ -1510,6 +1512,7 @@
       ir/store_test.cc
       ir/transform/add_empty_entry_point_test.cc
       ir/unary_test.cc
+      ir/validate_test.cc
     )
   endif()
 
diff --git a/src/tint/ir/branch.cc b/src/tint/ir/branch.cc
index 191831f..2c59c2c 100644
--- a/src/tint/ir/branch.cc
+++ b/src/tint/ir/branch.cc
@@ -24,6 +24,7 @@
 
 Branch::Branch(utils::VectorRef<Value*> args) : args_(std::move(args)) {
     for (auto* arg : args) {
+        TINT_ASSERT(IR, arg);
         arg->AddUsage(this);
     }
 }
diff --git a/src/tint/ir/validate.cc b/src/tint/ir/validate.cc
new file mode 100644
index 0000000..5e28173
--- /dev/null
+++ b/src/tint/ir/validate.cc
@@ -0,0 +1,131 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/validate.h"
+
+#include <utility>
+
+#include "src/tint/ir/function.h"
+#include "src/tint/ir/if.h"
+#include "src/tint/ir/loop.h"
+#include "src/tint/ir/return.h"
+#include "src/tint/ir/switch.h"
+#include "src/tint/ir/var.h"
+#include "src/tint/switch.h"
+#include "src/tint/type/pointer.h"
+
+namespace tint::ir {
+namespace {
+
+class Validator {
+  public:
+    explicit Validator(const Module& mod) : mod_(mod) {}
+
+    ~Validator() {}
+
+    utils::Result<Success, diag::List> IsValid() {
+        CheckRootBlock(mod_.root_block);
+
+        for (const auto* func : mod_.functions) {
+            CheckFunction(func);
+        }
+
+        if (diagnostics_.contains_errors()) {
+            return std::move(diagnostics_);
+        }
+        return Success{};
+    }
+
+  private:
+    const Module& mod_;
+    diag::List diagnostics_;
+
+    void AddError(const std::string& err) { diagnostics_.add_error(tint::diag::System::IR, err); }
+
+    std::string Name(const Value* v) { return mod_.NameOf(v).Name(); }
+
+    void CheckRootBlock(const Block* blk) {
+        if (!blk) {
+            return;
+        }
+
+        for (const auto* inst : *blk) {
+            auto* var = inst->As<ir::Var>();
+            if (!var) {
+                AddError(std::string("root block: invalid instruction: ") + inst->TypeInfo().name);
+                continue;
+            }
+            if (!var->Type()->Is<type::Pointer>()) {
+                AddError(std::string("root block: 'var' ") + Name(var) +
+                         "type is not a pointer: " + var->Type()->TypeInfo().name);
+            }
+        }
+    }
+
+    void CheckFunction(const Function* func) {
+        for (const auto* param : func->Params()) {
+            if (param == nullptr) {
+                AddError("function '" + Name(func) + "': null parameter");
+                continue;
+            }
+        }
+
+        if (func->StartTarget() == nullptr) {
+            AddError("function '" + Name(func) + "': null start target");
+        } else {
+            CheckBlock(func->StartTarget());
+        }
+    }
+
+    void CheckBlock(const Block* blk) {
+        if (!blk->HasBranchTarget()) {
+            AddError("block: does not end in a branch");
+        }
+
+        for (const auto* inst : *blk) {
+            if (inst->Is<ir::Branch>() && inst != blk->Branch()) {
+                AddError("block: branch which isn't the final instruction");
+                continue;
+            }
+
+            CheckInstruction(inst);
+        }
+    }
+
+    void CheckInstruction(const Instruction* inst) {
+        tint::Switch(
+            inst,  //
+            [&](const ir::Return* ret) {
+                if (ret->Func() == nullptr) {
+                    AddError("return: null function");
+                }
+            },
+            [&](Default) {
+                AddError(std::string("missing validation of: ") + inst->TypeInfo().name);
+            });
+    }
+};
+
+}  // namespace
+
+utils::Result<Success, std::string> Validate(const Module& mod) {
+    Validator v(mod);
+    auto r = v.IsValid();
+    if (!r) {
+        return r.Failure().str();
+    }
+    return Success{};
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/validate.h b/src/tint/ir/validate.h
new file mode 100644
index 0000000..233295b
--- /dev/null
+++ b/src/tint/ir/validate.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_VALIDATE_H_
+#define SRC_TINT_IR_VALIDATE_H_
+
+#include <string>
+
+#include "src/tint/ir/module.h"
+#include "src/tint/utils/result.h"
+
+namespace tint::ir {
+
+/// Signifies the validation completed successfully
+struct Success {};
+
+/// Validates that a given IR module is correctly formed
+/// @param mod the module to validate
+/// @returns true on success, an error result otherwise
+utils::Result<Success, std::string> Validate(const Module& mod);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_VALIDATE_H_
diff --git a/src/tint/ir/validate_test.cc b/src/tint/ir/validate_test.cc
new file mode 100644
index 0000000..9c3676c
--- /dev/null
+++ b/src/tint/ir/validate_test.cc
@@ -0,0 +1,105 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/validate.h"
+#include "gmock/gmock.h"
+#include "src/tint/ir/builder.h"
+#include "src/tint/ir/ir_test_helper.h"
+#include "src/tint/type/pointer.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_ValidateTest = IRTestHelper;
+
+TEST_F(IR_ValidateTest, RootBlock_Var) {
+    mod.root_block = b.CreateRootBlockIfNeeded();
+    mod.root_block->Append(b.Declare(mod.Types().pointer(
+        mod.Types().i32(), builtin::AddressSpace::kPrivate, builtin::Access::kReadWrite)));
+    auto res = ir::Validate(mod);
+    EXPECT_TRUE(res) << res.Failure();
+}
+
+TEST_F(IR_ValidateTest, RootBlock_NonVar) {
+    mod.root_block = b.CreateRootBlockIfNeeded();
+    mod.root_block->Append(b.CreateLoop());
+
+    auto res = ir::Validate(mod);
+    ASSERT_FALSE(res);
+    EXPECT_EQ(res.Failure(), "error: root block: invalid instruction: tint::ir::Loop");
+}
+
+TEST_F(IR_ValidateTest, RootBlock_VarBadType) {
+    mod.root_block = b.CreateRootBlockIfNeeded();
+    mod.root_block->Append(b.Declare(mod.Types().i32()));
+    auto res = ir::Validate(mod);
+    ASSERT_FALSE(res);
+    EXPECT_EQ(res.Failure(), "error: root block: 'var' type is not a pointer: tint::type::I32");
+}
+
+TEST_F(IR_ValidateTest, Function) {
+    auto* f = b.CreateFunction("my_func", mod.Types().void_());
+    mod.functions.Push(f);
+
+    f->SetParams(
+        utils::Vector{b.FunctionParam(mod.Types().i32()), b.FunctionParam(mod.Types().f32())});
+    f->StartTarget()->SetInstructions(utils::Vector{b.Return(f)});
+    auto res = ir::Validate(mod);
+    EXPECT_TRUE(res) << res.Failure();
+}
+
+TEST_F(IR_ValidateTest, Function_NullStartTarget) {
+    auto* f = b.CreateFunction("my_func", mod.Types().void_());
+    mod.functions.Push(f);
+
+    f->SetStartTarget(nullptr);
+    auto res = ir::Validate(mod);
+    ASSERT_FALSE(res);
+    EXPECT_EQ(res.Failure(), "error: function 'my_func': null start target");
+}
+
+TEST_F(IR_ValidateTest, Function_ParamNull) {
+    auto* f = b.CreateFunction("my_func", mod.Types().void_());
+    mod.functions.Push(f);
+
+    f->SetParams(utils::Vector<FunctionParam*, 1>{nullptr});
+    f->StartTarget()->SetInstructions(utils::Vector{b.Return(f)});
+    auto res = ir::Validate(mod);
+    ASSERT_FALSE(res);
+    EXPECT_EQ(res.Failure(), "error: function 'my_func': null parameter");
+}
+
+TEST_F(IR_ValidateTest, Block_NoBranchAtEnd) {
+    auto* f = b.CreateFunction("my_func", mod.Types().void_());
+    mod.functions.Push(f);
+
+    auto res = ir::Validate(mod);
+    ASSERT_FALSE(res);
+    EXPECT_EQ(res.Failure(), "error: block: does not end in a branch");
+}
+
+TEST_F(IR_ValidateTest, Block_BranchInMiddle) {
+    auto* f = b.CreateFunction("my_func", mod.Types().void_());
+    mod.functions.Push(f);
+
+    f->StartTarget()->SetInstructions(utils::Vector{b.Return(f), b.Return(f)});
+    auto res = ir::Validate(mod);
+    ASSERT_FALSE(res);
+    EXPECT_EQ(res.Failure(), "error: block: branch which isn't the final instruction");
+}
+
+}  // namespace
+}  // namespace tint::ir