//===- mlir/unittest/IR/VerifierTest.cpp - Verifier unit tests ------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "mlir/IR/Verifier.h" #include "mlir/IR/Block.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/MLIRContext.h" #include "mlir/IR/Operation.h" #include "mlir/IR/OperationSupport.h" #include "mlir/IR/Region.h" #include "gtest/gtest.h" using namespace mlir; namespace { TEST(VerifierTest, CrossContextResultType) { MLIRContext ctxA, ctxB; ctxA.allowUnregisteredDialects(); ctxB.allowUnregisteredDialects(); // Result type comes from ctxB but the op lives in ctxA. Type i32B = IntegerType::get(&ctxB, 32); Operation *op = Operation::create(UnknownLoc::get(&ctxA), OperationName("foo.bar", &ctxA), {i32B}, {}, NamedAttrList(), PropertyRef(), {}, 0); ScopedDiagnosticHandler suppress(&ctxA, [](Diagnostic &) { return success(); }); EXPECT_TRUE(failed(verify(op))); op->destroy(); } TEST(VerifierTest, CrossContextDiscardableAttr) { MLIRContext ctxA, ctxB; ctxA.allowUnregisteredDialects(); ctxB.allowUnregisteredDialects(); // Attribute value comes from ctxB but the op lives in ctxA. IntegerAttr attrB = IntegerAttr::get(IntegerType::get(&ctxB, 32), 42); NamedAttrList attrs; attrs.append(StringAttr::get(&ctxA, "my_attr"), attrB); Operation *op = Operation::create(UnknownLoc::get(&ctxA), OperationName("foo.bar", &ctxA), {}, {}, std::move(attrs), PropertyRef(), {}, 0); ScopedDiagnosticHandler suppress(&ctxA, [](Diagnostic &) { return success(); }); EXPECT_TRUE(failed(verify(op))); op->destroy(); } TEST(VerifierTest, CrossContextOperand) { MLIRContext ctxA, ctxB; ctxA.allowUnregisteredDialects(); ctxB.allowUnregisteredDialects(); // Producer op lives in ctxB; its result type is also from ctxB. Type i32B = IntegerType::get(&ctxB, 32); Operation *producerOp = Operation::create( UnknownLoc::get(&ctxB), OperationName("foo.producer", &ctxB), {i32B}, {}, NamedAttrList(), PropertyRef(), {}, 0); Value valFromCtxB = producerOp->getResult(0); // Consumer op lives in ctxA but uses the value (whose type is in ctxB). Operation *consumerOp = Operation::create( UnknownLoc::get(&ctxA), OperationName("foo.consumer", &ctxA), {}, {valFromCtxB}, NamedAttrList(), PropertyRef(), {}, 0); ScopedDiagnosticHandler suppress(&ctxA, [](Diagnostic &) { return success(); }); EXPECT_TRUE(failed(verify(consumerOp))); consumerOp->destroy(); producerOp->destroy(); } TEST(VerifierTest, CrossContextOperationName) { MLIRContext ctxA, ctxB; ctxA.allowUnregisteredDialects(); ctxB.allowUnregisteredDialects(); // Location (and thus op.getContext()) is from ctxA; OperationName is from // ctxB. op.getContext() == op.getLoc().getContext() by definition, so the // OperationName is the independent source of a cross-context violation here. Operation *op = Operation::create(UnknownLoc::get(&ctxA), OperationName("foo.bar", &ctxB), {}, {}, NamedAttrList(), PropertyRef(), {}, 0); ScopedDiagnosticHandler suppress(&ctxA, [](Diagnostic &) { return success(); }); EXPECT_TRUE(failed(verify(op))); op->destroy(); } TEST(VerifierTest, CrossContextOperationLocation) { MLIRContext ctxA, ctxB; ctxA.allowUnregisteredDialects(); ctxB.allowUnregisteredDialects(); // Create an outer op in ctxA with one region. Operation *outerOp = Operation::create( UnknownLoc::get(&ctxA), OperationName("foo.outer", &ctxA), {}, {}, NamedAttrList(), PropertyRef(), {}, /*numRegions=*/1); Block *block = new Block(); outerOp->getRegion(0).push_back(block); // Create an inner op whose location (and thus context) comes from ctxB, and // place it inside the ctxA block. This is the cross-context violation: // op.getContext() == ctxB but the enclosing block belongs to ctxA. Operation *innerOp = Operation::create( UnknownLoc::get(&ctxB), OperationName("foo.inner", &ctxB), {}, {}, NamedAttrList(), PropertyRef(), {}, 0); block->push_back(innerOp); // The verifier emits via the parent op's location (ctxA), so only ctxA's // handler needs to suppress it. ScopedDiagnosticHandler suppress(&ctxA, [](Diagnostic &) { return success(); }); EXPECT_TRUE(failed(verify(outerOp))); outerOp->destroy(); } TEST(VerifierTest, CrossContextBlockArgType) { MLIRContext ctxA, ctxB; ctxA.allowUnregisteredDialects(); ctxB.allowUnregisteredDialects(); // Create an unregistered op in ctxA with one region. Operation *op = Operation::create( UnknownLoc::get(&ctxA), OperationName("foo.bar", &ctxA), {}, {}, NamedAttrList(), PropertyRef(), {}, /*numRegions=*/1); // Add a block with one argument whose type comes from ctxB. Block *block = new Block(); op->getRegion(0).push_back(block); Type i32B = IntegerType::get(&ctxB, 32); block->addArgument(i32B, UnknownLoc::get(&ctxA)); ScopedDiagnosticHandler suppress(&ctxA, [](Diagnostic &) { return success(); }); EXPECT_TRUE(failed(verify(op))); op->destroy(); } TEST(VerifierTest, CrossContextBlockArgLocation) { MLIRContext ctxA, ctxB; ctxA.allowUnregisteredDialects(); ctxB.allowUnregisteredDialects(); // Create an unregistered op in ctxA with one region. Operation *op = Operation::create( UnknownLoc::get(&ctxA), OperationName("foo.bar", &ctxA), {}, {}, NamedAttrList(), PropertyRef(), {}, /*numRegions=*/1); // Add a block with one argument whose location comes from ctxB. Block *block = new Block(); op->getRegion(0).push_back(block); Type i32A = IntegerType::get(&ctxA, 32); block->addArgument(i32A, UnknownLoc::get(&ctxB)); // The verifier emits the diagnostic via the parent op's location (ctxA). ScopedDiagnosticHandler suppress(&ctxA, [](Diagnostic &) { return success(); }); EXPECT_TRUE(failed(verify(op))); op->destroy(); } } // namespace