[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