llvm-project/mlir/unittests/Interfaces/ControlFlowInterfacesTest.cpp
Matthias Springer 5f3b40ec7a
[mlir][Interfaces][NFC] Simplify and align RegionSuccessor design / API (#174945)
Simplify the design of `RegionSuccessor`. There is no need to store the
`Operation *` pointer when branching out of the region branch op (to the
parent). There is no API to even access the `Operation *` pointer.

Add a new helper function `RegionSuccessor::parent` to construct a
region successor that points to the parent. This aligns the
`RegionSuccessor` design and API with `RegionBranchPoint`:
* Both classes now have a `parent()` helper function.
`ClassName::parent()` can be used in documentation to precisely describe
the source/target of a region branch.
* Both classes now use `nullptr` internally to represent "parent".

This API change also protects against incorrect API usage: users can no
longer pass an incorrect parent op. If a region successor is not a
region of the region branch op, it *must* branch out of region branch op
itself ("parent"). However, the previous API allowed passing other
operations. There was one such API violation in a [test
case](https://github.com/llvm/llvm-project/pull/174945/files#diff-d5717e4a8d7344b2ff77762b8fa480bcfec0eeee97a86195c787d791a6217e13L71).

Also clean up the documentation to use the correct terminology (such as
"successor operands", "successor inputs") consistently.

Note: This PR effectively rolls back some changes from #161575. That PR
introduced `llvm::PointerUnion<Region *, Operation *>
successor{nullptr};`. It is unclear from the commit message why that
change was made.

Note for LLVM integration: You may have to slightly modify
`getSuccessorRegion` implementations: Replace
`RegionSuccessor(getOperation(), getOperation()->getResults())` with
`RegionSuccessor::parent(getResults())`.
2026-01-14 10:57:22 +01:00

277 lines
9.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 "llvm/Support/DebugLog.h"
#include <gtest/gtest.h>
using namespace mlir;
/// A dummy op that is also a terminator.
struct DummyOp : public Op<DummyOp, OpTrait::IsTerminator, OpTrait::ZeroResults,
OpTrait::ZeroSuccessors,
RegionBranchTerminatorOpInterface::Trait> {
using Op::Op;
static ArrayRef<StringRef> getAttributeNames() { return {}; }
static StringRef getOperationName() { return "cftest.dummy_op"; }
MutableOperandRange getMutableSuccessorOperands(RegionSuccessor point) {
return MutableOperandRange(getOperation(), 0, 0);
}
};
/// 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> &regions) {}
using RegionBranchOpInterface::Trait<
MutuallyExclusiveRegionsOp>::getSuccessorRegions;
};
/// 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> &regions) {
if (point.getTerminatorPredecessorOrNull()) {
Region *region =
point.getTerminatorPredecessorOrNull()->getParentRegion();
if (region == &(*this)->getRegion(1))
// This region also branches back to the parent.
regions.push_back(
RegionSuccessor::parent(getOperation()->getResults()));
regions.push_back(RegionSuccessor(region));
}
}
using RegionBranchOpInterface::Trait<LoopRegionsOp>::getSuccessorRegions;
};
/// 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> &regions) {
if (point.getTerminatorPredecessorOrNull()) {
Region *region =
point.getTerminatorPredecessorOrNull()->getParentRegion();
regions.push_back(RegionSuccessor::parent(getOperation()->getResults()));
regions.push_back(RegionSuccessor(region));
}
}
using RegionBranchOpInterface::Trait<
DoubleLoopRegionsOp>::getSuccessorRegions;
};
/// 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> &regions) {
if (point.getTerminatorPredecessorOrNull() &&
point.getTerminatorPredecessorOrNull()->getParentRegion() ==
&(*this)->getRegion(0)) {
Operation *thisOp = this->getOperation();
regions.push_back(RegionSuccessor(&thisOp->getRegion(1)));
}
}
using RegionBranchOpInterface::Trait<
SequentialRegionsOp>::getSuccessorRegions;
};
/// 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);
}