The current implementation is not very ergonomic or descriptive: It uses `std::optional<unsigned>` where `std::nullopt` represents the parent op and `unsigned` is the region number. This doesn't give us any useful methods specific to region control flow and makes the code fragile to changes due to now taking the region number into account. This patch introduces a new type called `RegionBranchPoint`, replacing all uses of `std::optional<unsigned>` in the interface. It can be implicitly constructed from a region or a `RegionSuccessor`, can be compared with a region to check whether the branch point is branching from the parent, adds `isParent` to check whether we are coming from a parent op and adds `RegionSuccessor::parent` as a descriptive way to indicate branching from the parent. Differential Revision: https://reviews.llvm.org/D159116
255 lines
8.6 KiB
C++
255 lines
8.6 KiB
C++
//===- ControlFlowInterfacesTest.cpp - Unit Tests for Control Flow Interf. ===//
|
|
//
|
|
// 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/Interfaces/ControlFlowInterfaces.h"
|
|
#include "mlir/IR/BuiltinOps.h"
|
|
#include "mlir/IR/Dialect.h"
|
|
#include "mlir/IR/DialectImplementation.h"
|
|
#include "mlir/IR/OpDefinition.h"
|
|
#include "mlir/IR/OpImplementation.h"
|
|
#include "mlir/Parser/Parser.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
using namespace mlir;
|
|
|
|
/// A dummy op that is also a terminator.
|
|
struct DummyOp : public Op<DummyOp, OpTrait::IsTerminator> {
|
|
using Op::Op;
|
|
static ArrayRef<StringRef> getAttributeNames() { return {}; }
|
|
|
|
static StringRef getOperationName() { return "cftest.dummy_op"; }
|
|
};
|
|
|
|
/// All regions of this op are mutually exclusive.
|
|
struct MutuallyExclusiveRegionsOp
|
|
: public Op<MutuallyExclusiveRegionsOp, RegionBranchOpInterface::Trait> {
|
|
using Op::Op;
|
|
static ArrayRef<StringRef> getAttributeNames() { return {}; }
|
|
|
|
static StringRef getOperationName() {
|
|
return "cftest.mutually_exclusive_regions_op";
|
|
}
|
|
|
|
// Regions have no successors.
|
|
void getSuccessorRegions(RegionBranchPoint point,
|
|
SmallVectorImpl<RegionSuccessor> ®ions) {}
|
|
};
|
|
|
|
/// All regions of this op call each other in a large circle.
|
|
struct LoopRegionsOp
|
|
: public Op<LoopRegionsOp, RegionBranchOpInterface::Trait> {
|
|
using Op::Op;
|
|
static const unsigned kNumRegions = 3;
|
|
|
|
static ArrayRef<StringRef> getAttributeNames() { return {}; }
|
|
|
|
static StringRef getOperationName() { return "cftest.loop_regions_op"; }
|
|
|
|
void getSuccessorRegions(RegionBranchPoint point,
|
|
SmallVectorImpl<RegionSuccessor> ®ions) {
|
|
if (Region *region = point.getRegionOrNull()) {
|
|
if (point == (*this)->getRegion(1))
|
|
// This region also branches back to the parent.
|
|
regions.push_back(RegionSuccessor());
|
|
regions.push_back(RegionSuccessor(region));
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Each region branches back it itself or the parent.
|
|
struct DoubleLoopRegionsOp
|
|
: public Op<DoubleLoopRegionsOp, RegionBranchOpInterface::Trait> {
|
|
using Op::Op;
|
|
|
|
static ArrayRef<StringRef> getAttributeNames() { return {}; }
|
|
|
|
static StringRef getOperationName() {
|
|
return "cftest.double_loop_regions_op";
|
|
}
|
|
|
|
void getSuccessorRegions(RegionBranchPoint point,
|
|
SmallVectorImpl<RegionSuccessor> ®ions) {
|
|
if (Region *region = point.getRegionOrNull()) {
|
|
regions.push_back(RegionSuccessor());
|
|
regions.push_back(RegionSuccessor(region));
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Regions are executed sequentially.
|
|
struct SequentialRegionsOp
|
|
: public Op<SequentialRegionsOp, RegionBranchOpInterface::Trait> {
|
|
using Op::Op;
|
|
static ArrayRef<StringRef> getAttributeNames() { return {}; }
|
|
|
|
static StringRef getOperationName() { return "cftest.sequential_regions_op"; }
|
|
|
|
// Region 0 has Region 1 as a successor.
|
|
void getSuccessorRegions(RegionBranchPoint point,
|
|
SmallVectorImpl<RegionSuccessor> ®ions) {
|
|
if (point == (*this)->getRegion(0)) {
|
|
Operation *thisOp = this->getOperation();
|
|
regions.push_back(RegionSuccessor(&thisOp->getRegion(1)));
|
|
}
|
|
}
|
|
};
|
|
|
|
/// A dialect putting all the above together.
|
|
struct CFTestDialect : Dialect {
|
|
explicit CFTestDialect(MLIRContext *ctx)
|
|
: Dialect(getDialectNamespace(), ctx, TypeID::get<CFTestDialect>()) {
|
|
addOperations<DummyOp, MutuallyExclusiveRegionsOp, LoopRegionsOp,
|
|
DoubleLoopRegionsOp, SequentialRegionsOp>();
|
|
}
|
|
static StringRef getDialectNamespace() { return "cftest"; }
|
|
};
|
|
|
|
TEST(RegionBranchOpInterface, MutuallyExclusiveOps) {
|
|
const char *ir = R"MLIR(
|
|
"cftest.mutually_exclusive_regions_op"() (
|
|
{"cftest.dummy_op"() : () -> ()}, // op1
|
|
{"cftest.dummy_op"() : () -> ()} // op2
|
|
) : () -> ()
|
|
)MLIR";
|
|
|
|
DialectRegistry registry;
|
|
registry.insert<CFTestDialect>();
|
|
MLIRContext ctx(registry);
|
|
|
|
OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
|
|
Operation *testOp = &module->getBody()->getOperations().front();
|
|
Operation *op1 = &testOp->getRegion(0).front().front();
|
|
Operation *op2 = &testOp->getRegion(1).front().front();
|
|
|
|
EXPECT_TRUE(insideMutuallyExclusiveRegions(op1, op2));
|
|
EXPECT_TRUE(insideMutuallyExclusiveRegions(op2, op1));
|
|
}
|
|
|
|
TEST(RegionBranchOpInterface, MutuallyExclusiveOps2) {
|
|
const char *ir = R"MLIR(
|
|
"cftest.double_loop_regions_op"() (
|
|
{"cftest.dummy_op"() : () -> ()}, // op1
|
|
{"cftest.dummy_op"() : () -> ()} // op2
|
|
) : () -> ()
|
|
)MLIR";
|
|
|
|
DialectRegistry registry;
|
|
registry.insert<CFTestDialect>();
|
|
MLIRContext ctx(registry);
|
|
|
|
OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
|
|
Operation *testOp = &module->getBody()->getOperations().front();
|
|
Operation *op1 = &testOp->getRegion(0).front().front();
|
|
Operation *op2 = &testOp->getRegion(1).front().front();
|
|
|
|
EXPECT_TRUE(insideMutuallyExclusiveRegions(op1, op2));
|
|
EXPECT_TRUE(insideMutuallyExclusiveRegions(op2, op1));
|
|
}
|
|
|
|
TEST(RegionBranchOpInterface, NotMutuallyExclusiveOps) {
|
|
const char *ir = R"MLIR(
|
|
"cftest.sequential_regions_op"() (
|
|
{"cftest.dummy_op"() : () -> ()}, // op1
|
|
{"cftest.dummy_op"() : () -> ()} // op2
|
|
) : () -> ()
|
|
)MLIR";
|
|
|
|
DialectRegistry registry;
|
|
registry.insert<CFTestDialect>();
|
|
MLIRContext ctx(registry);
|
|
|
|
OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
|
|
Operation *testOp = &module->getBody()->getOperations().front();
|
|
Operation *op1 = &testOp->getRegion(0).front().front();
|
|
Operation *op2 = &testOp->getRegion(1).front().front();
|
|
|
|
EXPECT_FALSE(insideMutuallyExclusiveRegions(op1, op2));
|
|
EXPECT_FALSE(insideMutuallyExclusiveRegions(op2, op1));
|
|
}
|
|
|
|
TEST(RegionBranchOpInterface, NestedMutuallyExclusiveOps) {
|
|
const char *ir = R"MLIR(
|
|
"cftest.mutually_exclusive_regions_op"() (
|
|
{
|
|
"cftest.sequential_regions_op"() (
|
|
{"cftest.dummy_op"() : () -> ()}, // op1
|
|
{"cftest.dummy_op"() : () -> ()} // op3
|
|
) : () -> ()
|
|
"cftest.dummy_op"() : () -> ()
|
|
},
|
|
{"cftest.dummy_op"() : () -> ()} // op2
|
|
) : () -> ()
|
|
)MLIR";
|
|
|
|
DialectRegistry registry;
|
|
registry.insert<CFTestDialect>();
|
|
MLIRContext ctx(registry);
|
|
|
|
OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
|
|
Operation *testOp = &module->getBody()->getOperations().front();
|
|
Operation *op1 =
|
|
&testOp->getRegion(0).front().front().getRegion(0).front().front();
|
|
Operation *op2 = &testOp->getRegion(1).front().front();
|
|
Operation *op3 =
|
|
&testOp->getRegion(0).front().front().getRegion(1).front().front();
|
|
|
|
EXPECT_TRUE(insideMutuallyExclusiveRegions(op1, op2));
|
|
EXPECT_TRUE(insideMutuallyExclusiveRegions(op3, op2));
|
|
EXPECT_FALSE(insideMutuallyExclusiveRegions(op1, op3));
|
|
}
|
|
|
|
TEST(RegionBranchOpInterface, RecursiveRegions) {
|
|
const char *ir = R"MLIR(
|
|
"cftest.loop_regions_op"() (
|
|
{"cftest.dummy_op"() : () -> ()}, // op1
|
|
{"cftest.dummy_op"() : () -> ()}, // op2
|
|
{"cftest.dummy_op"() : () -> ()} // op3
|
|
) : () -> ()
|
|
)MLIR";
|
|
|
|
DialectRegistry registry;
|
|
registry.insert<CFTestDialect>();
|
|
MLIRContext ctx(registry);
|
|
|
|
OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
|
|
Operation *testOp = &module->getBody()->getOperations().front();
|
|
auto regionOp = cast<RegionBranchOpInterface>(testOp);
|
|
Operation *op1 = &testOp->getRegion(0).front().front();
|
|
Operation *op2 = &testOp->getRegion(1).front().front();
|
|
Operation *op3 = &testOp->getRegion(2).front().front();
|
|
|
|
EXPECT_TRUE(regionOp.isRepetitiveRegion(0));
|
|
EXPECT_TRUE(regionOp.isRepetitiveRegion(1));
|
|
EXPECT_TRUE(regionOp.isRepetitiveRegion(2));
|
|
EXPECT_NE(getEnclosingRepetitiveRegion(op1), nullptr);
|
|
EXPECT_NE(getEnclosingRepetitiveRegion(op2), nullptr);
|
|
EXPECT_NE(getEnclosingRepetitiveRegion(op3), nullptr);
|
|
}
|
|
|
|
TEST(RegionBranchOpInterface, NotRecursiveRegions) {
|
|
const char *ir = R"MLIR(
|
|
"cftest.sequential_regions_op"() (
|
|
{"cftest.dummy_op"() : () -> ()}, // op1
|
|
{"cftest.dummy_op"() : () -> ()} // op2
|
|
) : () -> ()
|
|
)MLIR";
|
|
|
|
DialectRegistry registry;
|
|
registry.insert<CFTestDialect>();
|
|
MLIRContext ctx(registry);
|
|
|
|
OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &ctx);
|
|
Operation *testOp = &module->getBody()->getOperations().front();
|
|
Operation *op1 = &testOp->getRegion(0).front().front();
|
|
Operation *op2 = &testOp->getRegion(1).front().front();
|
|
|
|
EXPECT_EQ(getEnclosingRepetitiveRegion(op1), nullptr);
|
|
EXPECT_EQ(getEnclosingRepetitiveRegion(op2), nullptr);
|
|
}
|