Author: Henrich Lauko Date: 2026-09-04T14:11:27+02:00 New Revision: c5da0e956e2213fc9045df2f878bd3f6a3938fd5
URL: https://github.com/llvm/llvm-project/commit/c5da0e956e2213fc9045df2f878bd3f6a3938fd5 DIFF: https://github.com/llvm/llvm-project/commit/c5da0e956e2213fc9045df2f878bd3f6a3938fd5.diff LOG: [CIR] Add RegionBranchOpInterface unit tests and fix cir.await successors (#220507) Adds unit tests for the five CIR ops that implement `RegionBranchOpInterface` with no coverage today, and fixes a `cir.await` bug the new tests found: `verifyControlFlowInterfaceConsistency` fails on `cir.await` today, because the op and its own terminator disagree. `cir.condition` terminates the ready region and reports `{resume, suspend}` when the parent is an await, while `AwaitOp::getSuccessorRegions` listed all three regions as entry successors and reported the parent op as the successor of every region exit. The corrected edges match what `cir.condition` already reports: - from the parent, the only entry successor is `ready` - from `ready`, the successors are `resume` and `suspend` - from `suspend` or `resume`, the successor is the parent operation Added: Modified: clang/lib/CIR/Dialect/IR/CIRDialect.cpp clang/unittests/CIR/ControlFlowTest.cpp Removed: ################################################################################ diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index ac4a996783683..ab5791548262e 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -3262,19 +3262,32 @@ void cir::AwaitOp::build(OpBuilder &builder, OperationState &result, void cir::AwaitOp::getSuccessorRegions( mlir::RegionBranchPoint point, SmallVectorImpl<RegionSuccessor> ®ions) { - // If any index all the underlying regions branch back to the parent - // operation. - if (!point.isParent()) { - regions.emplace_back(getOperation()); + assert(point.isParent() || point.getTerminatorPredecessorOrNull()); + + // Execution always starts in the ready region. + if (point.isParent()) { + regions.emplace_back(&getReady()); return; } + mlir::Region *parentRegion = + point.getTerminatorPredecessorOrNull()->getParentRegion(); + + // Branching from ready: the cir.condition terminating it selects between + // suspending and resuming. Keep in sync with + // ConditionOp::getSuccessorRegions. + // // TODO: retrieve information from the promise and only push the // necessary ones. Example: `std::suspend_never` on initial or final // await's might allow suspend region to be skipped. - regions.push_back(RegionSuccessor(&this->getReady())); - regions.push_back(RegionSuccessor(&this->getSuspend())); - regions.push_back(RegionSuccessor(&this->getResume())); + if (&getReady() == parentRegion) { + regions.emplace_back(&getResume()); + regions.emplace_back(&getSuspend()); + return; + } + + // Branching from suspend or resume: exit to the parent operation. + regions.emplace_back(getOperation()); } mlir::ValueRange cir::AwaitOp::getSuccessorInputs(RegionSuccessor successor) { diff --git a/clang/unittests/CIR/ControlFlowTest.cpp b/clang/unittests/CIR/ControlFlowTest.cpp index 07df0bb839364..d6b9fda5235e6 100644 --- a/clang/unittests/CIR/ControlFlowTest.cpp +++ b/clang/unittests/CIR/ControlFlowTest.cpp @@ -320,6 +320,32 @@ TEST_F(CIRControlFlowTest, SwitchOp) { 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) { @@ -487,6 +513,67 @@ TEST_F(CIRControlFlowTest, ForOpWithCleanup) { 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 @@ -525,3 +612,71 @@ TEST_F(CIRControlFlowTest, TryOpWithCatchAll) { // TODO: TryOp::getSuccessorInputs returns empty for handler regions that // have block arguments, so verifyControlFlowInterfaceConsistency fails. } + +TEST_F(CIRControlFlowTest, CoroBodyOp) { + // A coroutine body must contain at least one cir.await. + OwningOpRef<ModuleOp> module = parse(R"CIR( + cir.func coroutine @f(%arg0 : !cir.bool) { + cir.coro.body { + cir.await(user, ready : { + cir.condition(%arg0) + }, suspend : { + cir.yield + }, resume : { + cir.yield + },) + cir.yield + } + cir.return + } + )CIR"); + auto coroBodyOp = findFirstOp<cir::CoroBodyOp>(*module); + + expectSuccessors(coroBodyOp, RegionBranchPoint::parent(), + {&coroBodyOp.getBody()}); + expectTerminatorSuccessors(coroBodyOp.getBody(), {nullptr}); + + RegionBranchOpInterface coroBranch = asRegionBranch(coroBodyOp); + EXPECT_FALSE(coroBranch.isRepetitiveRegion(0)); + EXPECT_FALSE(coroBranch.hasLoop()); + + verifyControlFlowInterfaceConsistency(coroBodyOp); +} + +TEST_F(CIRControlFlowTest, AwaitOp) { + OwningOpRef<ModuleOp> module = parse(R"CIR( + cir.func coroutine @f(%arg0 : !cir.bool) { + cir.coro.body { + cir.await(user, ready : { + cir.condition(%arg0) + }, suspend : { + cir.yield + }, resume : { + cir.yield + },) + cir.yield + } + cir.return + } + )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); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
