[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