| //===- 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 ®ion) { |
| 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 ®ion, |
| 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 ®ion : 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 ®ion : op->getRegions()) { |
| if (region.empty()) |
| continue; |
| EXPECT_TRUE(allReachable.contains(®ion)) |
| << "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); |
| } |