blob: 47312d5e8843a198d09d977b427b5885a1d17119 [file] [edit]
//===- ControlFlowTest.cpp - Unit tests for CIR control flow interfaces ---===//
//
// 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/BuiltinOps.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OwningOpRef.h"
#include "mlir/IR/Verifier.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Parser/Parser.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "llvm/ADT/SmallPtrSet.h"
#include <gtest/gtest.h>
using namespace mlir;
//===----------------------------------------------------------------------===//
// Test helpers
//===----------------------------------------------------------------------===//
/// Use nullptr in `expected` to represent the parent (exit).
static void expectRegionSuccessors(ArrayRef<RegionSuccessor> actual,
ArrayRef<Region *> expected,
StringRef label) {
EXPECT_EQ(actual.size(), expected.size())
<< "successor count mismatch from " << label.str();
for (Region *r : expected)
EXPECT_TRUE(llvm::any_of(
actual,
[r](const RegionSuccessor &s) { return s.getSuccessor() == r; }))
<< "expected region "
<< (r ? std::to_string(r->getRegionNumber()) : "parent")
<< " not found in successors from " << label.str();
for (const RegionSuccessor &s : actual) {
Region *r = s.getSuccessor();
EXPECT_TRUE(llvm::is_contained(expected, r))
<< "unexpected region "
<< (r ? std::to_string(r->getRegionNumber()) : "parent")
<< " in successors from " << label.str();
}
}
/// Check that `op.getSuccessorRegions(point)` matches `expected`.
static void expectSuccessors(RegionBranchOpInterface op,
RegionBranchPoint point,
ArrayRef<Region *> expected) {
SmallVector<RegionSuccessor> successors;
op.getSuccessorRegions(point, successors);
expectRegionSuccessors(successors, expected,
point.isParent() ? "parent" : "region terminator");
}
/// Return the RegionBranchTerminatorOpInterface for the region's terminator,
/// failing the test if the terminator doesn't implement the interface.
static RegionBranchTerminatorOpInterface getTerminator(Region &region) {
Operation *term = region.front().getTerminator();
auto branchTerm = dyn_cast<RegionBranchTerminatorOpInterface>(term);
EXPECT_TRUE(branchTerm)
<< "terminator '" << term->getName().getStringRef().str()
<< "' in region #" << region.getRegionNumber()
<< " does not implement RegionBranchTerminatorOpInterface";
return branchTerm;
}
/// Check that the terminator of `region` has the given successor regions.
static void expectTerminatorSuccessors(Region &region,
ArrayRef<Region *> expected) {
RegionBranchTerminatorOpInterface term = getTerminator(region);
if (!term)
return;
SmallVector<RegionSuccessor> successors;
term.getSuccessorRegions(/*operands=*/{}, successors);
expectRegionSuccessors(successors, expected,
"region #" + std::to_string(region.getRegionNumber()));
}
/// Verify control flow interface consistency beyond what mlir::verify checks:
/// - Every non-empty region is reachable
/// - Every terminator implements RegionBranchTerminatorOpInterface
/// - Op and terminator agree on successor regions
static void verifyControlFlowInterfaceConsistency(RegionBranchOpInterface op) {
EXPECT_TRUE(succeeded(mlir::verify(op)))
<< "MLIR verifier failed for '" << op->getName().getStringRef().str()
<< "'";
SmallVector<RegionSuccessor> entrySuccessors;
op.getSuccessorRegions(RegionBranchPoint::parent(), entrySuccessors);
llvm::SmallPtrSet<Region *, 4> allReachable;
for (auto &succ : entrySuccessors)
allReachable.insert(succ.getSuccessor());
for (Region &region : op->getRegions()) {
if (region.empty())
continue;
RegionBranchTerminatorOpInterface term = getTerminator(region);
SmallVector<RegionSuccessor> opSuccessors;
op.getSuccessorRegions(region, opSuccessors);
for (auto &succ : opSuccessors)
allReachable.insert(succ.getSuccessor());
// Op and terminator should report the same successors.
if (term) {
SmallVector<RegionSuccessor> termSuccessors;
term.getSuccessorRegions(/*operands=*/{}, termSuccessors);
SmallPtrSet<Region *, 4> opSet, termSet;
for (auto &s : opSuccessors)
opSet.insert(s.getSuccessor());
for (auto &s : termSuccessors)
termSet.insert(s.getSuccessor());
EXPECT_EQ(opSet, termSet)
<< "op and terminator disagree on successors from region #"
<< region.getRegionNumber();
}
}
for (Region &region : op->getRegions()) {
if (region.empty())
continue;
EXPECT_TRUE(allReachable.contains(&region))
<< "Region #" << region.getRegionNumber()
<< " is non-empty but not reachable from any branch point";
}
EXPECT_TRUE(allReachable.contains(nullptr))
<< "parent (exit) not reachable from any branch point";
}
//===----------------------------------------------------------------------===//
// Test fixture
//===----------------------------------------------------------------------===//
class CIRControlFlowTest : public ::testing::Test {
protected:
CIRControlFlowTest() { context.loadDialect<cir::CIRDialect>(); }
OwningOpRef<ModuleOp> parse(StringRef ir) {
auto module = parseSourceString<ModuleOp>(ir, &context);
EXPECT_TRUE(module) << "failed to parse IR";
return module;
}
template <typename T> T findFirstOp(ModuleOp module) {
T result = nullptr;
module->walk([&](T op) {
result = op;
return WalkResult::interrupt();
});
EXPECT_NE(result, nullptr) << "op not found in module";
return result;
}
static RegionBranchOpInterface asRegionBranch(Operation *op) {
return cast<RegionBranchOpInterface>(op);
}
MLIRContext context;
};
//===----------------------------------------------------------------------===//
// Tests
//===----------------------------------------------------------------------===//
TEST_F(CIRControlFlowTest, IfOpThenOnly) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%cond : !cir.bool) {
cir.if %cond {
cir.yield
}
cir.return
}
)CIR");
auto ifOp = findFirstOp<cir::IfOp>(*module);
// Parent branches to then or exits (no else).
expectSuccessors(ifOp, RegionBranchPoint::parent(),
{&ifOp.getThenRegion(), nullptr});
expectTerminatorSuccessors(ifOp.getThenRegion(), {nullptr});
RegionBranchOpInterface ifBranch = asRegionBranch(ifOp);
EXPECT_FALSE(ifBranch.isRepetitiveRegion(0));
EXPECT_FALSE(ifBranch.hasLoop());
verifyControlFlowInterfaceConsistency(ifOp);
}
TEST_F(CIRControlFlowTest, IfOpThenElse) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%cond : !cir.bool) {
cir.if %cond {
cir.yield
} else {
cir.yield
}
cir.return
}
)CIR");
auto ifOp = findFirstOp<cir::IfOp>(*module);
expectSuccessors(ifOp, RegionBranchPoint::parent(),
{&ifOp.getThenRegion(), &ifOp.getElseRegion()});
expectTerminatorSuccessors(ifOp.getThenRegion(), {nullptr});
expectTerminatorSuccessors(ifOp.getElseRegion(), {nullptr});
RegionBranchOpInterface ifBranch = asRegionBranch(ifOp);
EXPECT_FALSE(ifBranch.isRepetitiveRegion(0));
EXPECT_FALSE(ifBranch.isRepetitiveRegion(1));
EXPECT_FALSE(ifBranch.hasLoop());
verifyControlFlowInterfaceConsistency(ifOp);
}
TEST_F(CIRControlFlowTest, ScopeOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f() {
cir.scope {
cir.yield
}
cir.return
}
)CIR");
auto scopeOp = findFirstOp<cir::ScopeOp>(*module);
expectSuccessors(scopeOp, RegionBranchPoint::parent(),
{&scopeOp.getScopeRegion()});
expectTerminatorSuccessors(scopeOp.getScopeRegion(), {nullptr});
RegionBranchOpInterface scopeBranch = asRegionBranch(scopeOp);
EXPECT_FALSE(scopeBranch.isRepetitiveRegion(0));
EXPECT_FALSE(scopeBranch.hasLoop());
verifyControlFlowInterfaceConsistency(scopeOp);
}
TEST_F(CIRControlFlowTest, ScopeOpWithResult) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!s32i = !cir.int<s, 32>
cir.func @f() -> !s32i {
%0 = cir.scope {
%c = cir.const #cir.int<42> : !s32i
cir.yield %c : !s32i
} : !s32i
cir.return %0 : !s32i
}
)CIR");
auto scopeOp = findFirstOp<cir::ScopeOp>(*module);
// getSuccessorInputs(parent) should return the scope's result.
ValueRange parentInputs =
scopeOp.getSuccessorInputs(mlir::RegionSuccessor(scopeOp));
EXPECT_EQ(parentInputs.size(), 1u);
// The yield's operands are forwarded to the parent result.
RegionBranchTerminatorOpInterface term =
getTerminator(scopeOp.getScopeRegion());
ASSERT_TRUE(term);
OperandRange yieldOperands =
term.getSuccessorOperands(mlir::RegionSuccessor(scopeOp));
EXPECT_EQ(yieldOperands.size(), 1u);
verifyControlFlowInterfaceConsistency(scopeOp);
}
TEST_F(CIRControlFlowTest, TernaryOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!u32i = !cir.int<u, 32>
cir.func @f(%cond : !cir.bool) -> !u32i {
%0 = cir.ternary(%cond, true {
%a = cir.const #cir.int<0> : !u32i
cir.yield %a : !u32i
}, false {
%b = cir.const #cir.int<1> : !u32i
cir.yield %b : !u32i
}) : (!cir.bool) -> !u32i
cir.return %0 : !u32i
}
)CIR");
auto ternOp = findFirstOp<cir::TernaryOp>(*module);
expectSuccessors(ternOp, RegionBranchPoint::parent(),
{&ternOp.getTrueRegion(), &ternOp.getFalseRegion()});
expectTerminatorSuccessors(ternOp.getTrueRegion(), {nullptr});
expectTerminatorSuccessors(ternOp.getFalseRegion(), {nullptr});
RegionBranchOpInterface ternBranch = asRegionBranch(ternOp);
EXPECT_FALSE(ternBranch.isRepetitiveRegion(0));
EXPECT_FALSE(ternBranch.isRepetitiveRegion(1));
EXPECT_FALSE(ternBranch.hasLoop());
verifyControlFlowInterfaceConsistency(ternOp);
}
TEST_F(CIRControlFlowTest, SwitchOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!s32i = !cir.int<s, 32>
cir.func @f(%val : !s32i) {
cir.switch (%val : !s32i) {
cir.case (equal, [#cir.int<1> : !s32i]) {
cir.yield
}
cir.case (default, []) {
cir.yield
}
cir.yield
}
cir.return
}
)CIR");
auto switchOp = findFirstOp<cir::SwitchOp>(*module);
expectSuccessors(switchOp, RegionBranchPoint::parent(),
{&switchOp.getBody()});
expectTerminatorSuccessors(switchOp.getBody(), {nullptr});
RegionBranchOpInterface switchBranch = asRegionBranch(switchOp);
EXPECT_FALSE(switchBranch.isRepetitiveRegion(0));
EXPECT_FALSE(switchBranch.hasLoop());
verifyControlFlowInterfaceConsistency(switchOp);
}
TEST_F(CIRControlFlowTest, CaseOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!s32i = !cir.int<s, 32>
cir.func @f(%val : !s32i) {
cir.switch (%val : !s32i) {
cir.case (equal, [#cir.int<1> : !s32i]) {
cir.yield
}
cir.yield
}
cir.return
}
)CIR");
auto caseOp = findFirstOp<cir::CaseOp>(*module);
expectSuccessors(caseOp, RegionBranchPoint::parent(),
{&caseOp.getCaseRegion()});
expectTerminatorSuccessors(caseOp.getCaseRegion(), {nullptr});
RegionBranchOpInterface caseBranch = asRegionBranch(caseOp);
EXPECT_FALSE(caseBranch.isRepetitiveRegion(0));
EXPECT_FALSE(caseBranch.hasLoop());
verifyControlFlowInterfaceConsistency(caseOp);
}
TEST_F(CIRControlFlowTest, WhileOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%cond : !cir.bool) {
cir.while {
cir.condition(%cond)
} do {
cir.yield
}
cir.return
}
)CIR");
auto whileOp = findFirstOp<cir::WhileOp>(*module);
// Parent enters the condition region.
expectSuccessors(whileOp, RegionBranchPoint::parent(), {&whileOp.getCond()});
// Condition branches to body or exits.
expectTerminatorSuccessors(whileOp.getCond(), {&whileOp.getBody(), nullptr});
// Body branches back to condition (loop back-edge).
expectTerminatorSuccessors(whileOp.getBody(), {&whileOp.getCond()});
RegionBranchOpInterface whileBranch = asRegionBranch(whileOp);
EXPECT_TRUE(whileBranch.isRepetitiveRegion(0)); // cond
EXPECT_TRUE(whileBranch.isRepetitiveRegion(1)); // body
EXPECT_TRUE(whileBranch.hasLoop());
verifyControlFlowInterfaceConsistency(whileOp);
}
TEST_F(CIRControlFlowTest, ForOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%cond : !cir.bool) {
cir.for : cond {
cir.condition(%cond)
} body {
cir.yield
} step {
cir.yield
}
cir.return
}
)CIR");
auto forOp = findFirstOp<cir::ForOp>(*module);
// Parent enters the condition region.
expectSuccessors(forOp, RegionBranchPoint::parent(), {&forOp.getCond()});
// Condition branches to body or exits.
expectTerminatorSuccessors(forOp.getCond(), {&forOp.getBody(), nullptr});
// Body goes to step.
expectTerminatorSuccessors(forOp.getBody(), {&forOp.getStep()});
// Step goes back to condition.
expectTerminatorSuccessors(forOp.getStep(), {&forOp.getCond()});
RegionBranchOpInterface forBranch = asRegionBranch(forOp);
EXPECT_TRUE(forBranch.isRepetitiveRegion(0)); // cond
EXPECT_TRUE(forBranch.isRepetitiveRegion(1)); // body
EXPECT_TRUE(forBranch.isRepetitiveRegion(2)); // step
EXPECT_TRUE(forBranch.hasLoop());
verifyControlFlowInterfaceConsistency(forOp);
}
TEST_F(CIRControlFlowTest, DoWhileOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%cond : !cir.bool) {
cir.do {
cir.yield
} while {
cir.condition(%cond)
}
cir.return
}
)CIR");
auto doWhileOp = findFirstOp<cir::DoWhileOp>(*module);
// Parent enters the body region (not condition).
expectSuccessors(doWhileOp, RegionBranchPoint::parent(),
{&doWhileOp.getBody()});
// Body goes to condition.
expectTerminatorSuccessors(doWhileOp.getBody(), {&doWhileOp.getCond()});
// Condition branches back to body or exits.
expectTerminatorSuccessors(doWhileOp.getCond(),
{&doWhileOp.getBody(), nullptr});
RegionBranchOpInterface doWhileBranch = asRegionBranch(doWhileOp);
EXPECT_TRUE(doWhileBranch.isRepetitiveRegion(0)); // body
EXPECT_TRUE(doWhileBranch.isRepetitiveRegion(1)); // cond
EXPECT_TRUE(doWhileBranch.hasLoop());
verifyControlFlowInterfaceConsistency(doWhileOp);
}
TEST_F(CIRControlFlowTest, WhileOpWithCleanup) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%cond : !cir.bool) {
cir.while {
cir.condition(%cond)
} do {
cir.yield
} cleanup all {
cir.yield
}
cir.return
}
)CIR");
auto whileOp = findFirstOp<cir::WhileOp>(*module);
Region *cleanup = whileOp.maybeGetCleanup();
ASSERT_NE(cleanup, nullptr);
// Parent enters the condition region.
expectSuccessors(whileOp, RegionBranchPoint::parent(), {&whileOp.getCond()});
// Condition branches to the body or, on the false edge, the cleanup region.
expectTerminatorSuccessors(whileOp.getCond(), {&whileOp.getBody(), cleanup});
// Body routes through the cleanup region (there is no step region).
expectTerminatorSuccessors(whileOp.getBody(), {cleanup});
// Cleanup loops back to the condition or exits the loop.
expectTerminatorSuccessors(*cleanup, {&whileOp.getCond(), nullptr});
verifyControlFlowInterfaceConsistency(whileOp);
}
TEST_F(CIRControlFlowTest, ForOpWithCleanup) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%cond : !cir.bool) {
cir.for : cond {
cir.condition(%cond)
} body {
cir.yield
} step {
cir.yield
} cleanup all {
cir.yield
}
cir.return
}
)CIR");
auto forOp = findFirstOp<cir::ForOp>(*module);
Region *cleanup = forOp.maybeGetCleanup();
ASSERT_NE(cleanup, nullptr);
// Parent enters the condition region.
expectSuccessors(forOp, RegionBranchPoint::parent(), {&forOp.getCond()});
// Condition branches to the body or, on the false edge, the cleanup region.
expectTerminatorSuccessors(forOp.getCond(), {&forOp.getBody(), cleanup});
// Body goes to the step region.
expectTerminatorSuccessors(forOp.getBody(), {&forOp.getStep()});
// Step routes through the cleanup region.
expectTerminatorSuccessors(forOp.getStep(), {cleanup});
// Cleanup loops back to the condition or exits the loop.
expectTerminatorSuccessors(*cleanup, {&forOp.getCond(), nullptr});
verifyControlFlowInterfaceConsistency(forOp);
}
TEST_F(CIRControlFlowTest, CleanupScopeOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f() {
cir.cleanup.scope {
cir.yield
} cleanup all {
cir.yield
}
cir.return
}
)CIR");
auto cleanupScopeOp = findFirstOp<cir::CleanupScopeOp>(*module);
expectSuccessors(
cleanupScopeOp, RegionBranchPoint::parent(),
{&cleanupScopeOp.getBodyRegion(), &cleanupScopeOp.getCleanupRegion()});
expectTerminatorSuccessors(cleanupScopeOp.getBodyRegion(), {nullptr});
expectTerminatorSuccessors(cleanupScopeOp.getCleanupRegion(), {nullptr});
RegionBranchOpInterface cleanupBranch = asRegionBranch(cleanupScopeOp);
EXPECT_FALSE(cleanupBranch.isRepetitiveRegion(0));
EXPECT_FALSE(cleanupBranch.isRepetitiveRegion(1));
EXPECT_FALSE(cleanupBranch.hasLoop());
verifyControlFlowInterfaceConsistency(cleanupScopeOp);
}
TEST_F(CIRControlFlowTest, GlobalOpWithCtorAndDtor) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!s32i = !cir.int<s, 32>
cir.global external @g = ctor : !s32i {
cir.yield
} dtor {
cir.yield
}
)CIR");
auto globalOp = findFirstOp<cir::GlobalOp>(*module);
expectSuccessors(globalOp, RegionBranchPoint::parent(),
{&globalOp.getCtorRegion(), &globalOp.getDtorRegion()});
expectTerminatorSuccessors(globalOp.getCtorRegion(), {nullptr});
expectTerminatorSuccessors(globalOp.getDtorRegion(), {nullptr});
EXPECT_FALSE(asRegionBranch(globalOp).hasLoop());
verifyControlFlowInterfaceConsistency(globalOp);
}
TEST_F(CIRControlFlowTest, GlobalOpWithoutRegions) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!s32i = !cir.int<s, 32>
cir.global external @g = #cir.int<0> : !s32i
)CIR");
auto globalOp = findFirstOp<cir::GlobalOp>(*module);
// A global with neither a ctor nor a dtor never enters a region, so it has
// no successors at all. verifyControlFlowInterfaceConsistency doesn't apply:
// it requires the parent to be reachable from some branch point.
expectSuccessors(globalOp, RegionBranchPoint::parent(), {});
}
TEST_F(CIRControlFlowTest, TryOpWithCatchAll) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!void = !cir.void
cir.func @f() {
cir.scope {
cir.try {
cir.yield
} catch all (%eh : !cir.eh_token) {
%ct, %exn = cir.begin_catch %eh
: !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
cir.cleanup.scope {
cir.yield
} cleanup eh {
cir.end_catch %ct : !cir.catch_token
cir.yield
}
cir.yield
}
}
cir.return
}
)CIR");
auto tryOp = findFirstOp<cir::TryOp>(*module);
Region &tryRegion = tryOp.getTryRegion();
MutableArrayRef<Region> handlerRegions = tryOp.getHandlerRegions();
ASSERT_EQ(handlerRegions.size(), 1u);
expectSuccessors(tryOp, RegionBranchPoint::parent(),
{&tryRegion, &handlerRegions[0]});
expectTerminatorSuccessors(tryRegion, {nullptr});
expectTerminatorSuccessors(handlerRegions[0], {nullptr});
EXPECT_FALSE(asRegionBranch(tryOp).hasLoop());
// TODO: TryOp::getSuccessorInputs returns empty for handler regions that
// have block arguments, so verifyControlFlowInterfaceConsistency fails.
}
TEST_F(CIRControlFlowTest, CoroutineOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func coroutine @f(%arg0 : !cir.bool) {
cir.coroutine initialSuspend : {
cir.await(init, ready : {
cir.condition(%arg0)
}, suspend : {
cir.coro.suspend_point
}, resume : {
cir.yield
},)
cir.yield
}, body : {
cir.yield
}, finalSuspend : {
cir.yield
}, destroy : {
cir.yield
}, exit : {
cir.return
}
cir.trap
}
)CIR");
auto coroOp = findFirstOp<cir::CoroutineOp>(*module);
// The parent only ever enters through initial_suspend.
expectSuccessors(coroOp, RegionBranchPoint::parent(),
{&coroOp.getInitialSuspend()});
// initial_suspend: falls into body once resumed, exits directly on a
// plain suspend, or reaches destroy on an explicit destroy() call.
RegionBranchTerminatorOpInterface initTerm =
getTerminator(coroOp.getInitialSuspend());
ASSERT_TRUE(initTerm);
expectSuccessors(coroOp, RegionBranchPoint(initTerm),
{&coroOp.getBody(), &coroOp.getExit()});
expectTerminatorSuccessors(coroOp.getInitialSuspend(),
{&coroOp.getBody(), &coroOp.getExit()});
// body: falls through to final_suspend, exits directly on a plain
// suspend, or reaches destroy
RegionBranchTerminatorOpInterface bodyTerm = getTerminator(coroOp.getBody());
ASSERT_TRUE(bodyTerm);
expectSuccessors(
coroOp, RegionBranchPoint(bodyTerm),
{&coroOp.getFinalSuspend(), &coroOp.getExit(), &coroOp.getDestroy()});
expectTerminatorSuccessors(
coroOp.getBody(),
{&coroOp.getFinalSuspend(), &coroOp.getExit(), &coroOp.getDestroy()});
// final_suspend: exits or destroy.
// Should always be destroy in practice, a real suspend after
// final_suspend is UB, so the exit edge is never actually taken by a
// valid program. Keeping exit here anyway for now.
RegionBranchTerminatorOpInterface finalTerm =
getTerminator(coroOp.getFinalSuspend());
ASSERT_TRUE(finalTerm);
expectSuccessors(coroOp, RegionBranchPoint(finalTerm),
{&coroOp.getExit(), &coroOp.getDestroy()});
expectTerminatorSuccessors(coroOp.getFinalSuspend(),
{&coroOp.getExit(), &coroOp.getDestroy()});
// destroy: ordinary dispatch falls through to exit;
RegionBranchTerminatorOpInterface destroyTerm =
getTerminator(coroOp.getDestroy());
ASSERT_TRUE(destroyTerm);
expectSuccessors(coroOp, RegionBranchPoint(destroyTerm),
{&coroOp.getExit(), nullptr});
expectTerminatorSuccessors(coroOp.getDestroy(), {&coroOp.getExit(), nullptr});
// TODO: cir.return doesn't implement RegionBranchTerminatorOpInterface
// exit always terminates the op.
// RegionBranchTerminatorOpInterface exitTerm =
// getTerminator(coroOp.getExit()); ASSERT_TRUE(exitTerm);
// expectSuccessors(coroOp, RegionBranchPoint(exitTerm), {nullptr});
// expectTerminatorSuccessors(coroOp.getExit(), {nullptr});
RegionBranchOpInterface coroBranch = asRegionBranch(coroOp);
EXPECT_FALSE(coroBranch.isRepetitiveRegion(0));
EXPECT_FALSE(coroBranch.isRepetitiveRegion(1));
EXPECT_FALSE(coroBranch.isRepetitiveRegion(2));
EXPECT_FALSE(coroBranch.isRepetitiveRegion(3));
EXPECT_FALSE(coroBranch.isRepetitiveRegion(4));
EXPECT_TRUE(coroBranch.hasLoop());
// TODO: cir.return doesn't implement RegionBranchTerminatorOpInterface
// verifyControlFlowInterfaceConsistency(coroOp);
}
TEST_F(CIRControlFlowTest, AwaitOp) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func coroutine @f(%arg0 : !cir.bool) {
cir.coroutine initialSuspend : {
cir.await(init, ready : {
cir.condition(%arg0)
}, suspend : {
cir.coro.suspend_point
}, resume : {
cir.yield
},)
cir.yield
}, body : {
cir.yield
}, finalSuspend : {
cir.yield
}, destroy : {
cir.yield
}, exit : {
cir.return
}
cir.trap
}
)CIR");
auto awaitOp = findFirstOp<cir::AwaitOp>(*module);
// Only the ready region is entered from the parent; suspend and resume are
// selected by the cir.condition terminating it.
expectSuccessors(awaitOp, RegionBranchPoint::parent(), {&awaitOp.getReady()});
RegionBranchTerminatorOpInterface readyTerm =
getTerminator(awaitOp.getReady());
ASSERT_TRUE(readyTerm);
expectSuccessors(awaitOp, RegionBranchPoint(readyTerm),
{&awaitOp.getResume(), &awaitOp.getSuspend()});
expectTerminatorSuccessors(awaitOp.getReady(),
{&awaitOp.getResume(), &awaitOp.getSuspend()});
expectTerminatorSuccessors(awaitOp.getSuspend(), {nullptr});
expectTerminatorSuccessors(awaitOp.getResume(), {nullptr});
EXPECT_FALSE(asRegionBranch(awaitOp).hasLoop());
verifyControlFlowInterfaceConsistency(awaitOp);
}