[clang] 37c927a - [CIR] Add missing Coroutine region successors (#230466)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 11 12:10:36 PDT 2026
Author: Morris Hafner
Date: 2026-10-11T21:10:32+02:00
New Revision: 37c927a7cc292aaac78d84a1debd62cbd4c71891
URL: https://github.com/llvm/llvm-project/commit/37c927a7cc292aaac78d84a1debd62cbd4c71891
DIFF: https://github.com/llvm/llvm-project/commit/37c927a7cc292aaac78d84a1debd62cbd4c71891.diff
LOG: [CIR] Add missing Coroutine region successors (#230466)
* A coroutine may be destroyed after its `initial_suspend`. Add an edge
from there to `destroy`
* `suspend` has no edge to `resume`. Add this, too
Added:
Modified:
clang/include/clang/CIR/Dialect/IR/CIROps.td
clang/lib/CIR/Dialect/IR/CIRDialect.cpp
clang/unittests/CIR/ControlFlowTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 6a7d8ca8f7fc9..babd9ed5a3d92 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4902,11 +4902,13 @@ def CIR_CoroutineOp : CIR_Op<"coroutine", [
points and introduce structured control flow that transfers
execution to `final_suspend`.
- - `final_suspend`: exactly one `cir.await`, marked `final`. Nothing
- else. Corresponds to `co_await promise.final_suspend()`. Resuming
- past this point is undefined behavior in valid programs, so its
- `resume` edge is expected to be dead in practice, but is kept for
- structural symmetry with the other two suspend regions.
+ - `final_suspend`: at most one `cir.await`, marked `final`. Nothing
+ else. Corresponds to `co_await promise.final_suspend()`. Suspending
+ at this final suspend point is valid: the coroutine stays suspended
+ until it is destroyed. Resuming past this point is undefined behavior
+ in valid programs, so its `resume` edge is expected to be dead in
+ practice, but is kept for structural symmetry with the other two
+ suspend regions.
- `gro`: TODO.
@@ -4914,10 +4916,10 @@ def CIR_CoroutineOp : CIR_Op<"coroutine", [
- `destroy`: destroys the coroutine frame (`coro.free` + `delete`).
Reached by an explicit `destroy()` call on a suspended handle, by
- normal completion through `final_suspend`, or by an exception
- escaping the coroutine. Falls through to `exit` except when reached
- due to an escaping exception, where it continues propagating that
- exception instead.
+ normal completion through `initial_suspend`, `final_suspend`, or
+ by an exception escaping the coroutine. Falls through to `exit`
+ except when reached due to an escaping exception, where it continues
+ propagating that exception instead.
- `exit`: the shared return-to-caller path. Reached directly from any
suspend point that isn't being destroyed (the coroutine is just
@@ -5061,10 +5063,9 @@ def CIR_CoReturnOp : CIR_Op<"co_return", [
]> {
let summary = "Coroutine return operation";
let description = [{
- The `cir.co_return` operation models a coroutine return point inside a
- `cir.coro.body` region.
- This operation is expected to appear only within a `cir.coro.body` region,
- but it may be nested within other operations or regions inside that body.
+ The `cir.co_return` operation models a coroutine return point inside the
+ `body` region of a `cir.coroutine`. It may be nested within other
+ operations or regions inside that body.
}];
let assemblyFormat = [{
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 80696e2f7e990..0fb9f585d2228 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3722,7 +3722,15 @@ void cir::AwaitOp::getSuccessorRegions(
return;
}
- // Branching from suspend or resume: exit to the parent operation.
+ // Branching from suspend: continue in resume once the coroutine is resumed,
+ // or exit to the parent operation if it stays suspended or is destroyed.
+ if (&getSuspend() == parentRegion) {
+ regions.emplace_back(&getResume());
+ regions.emplace_back(getOperation());
+ return;
+ }
+
+ // Branching from resume: exit to the parent operation.
regions.emplace_back(getOperation());
}
@@ -3807,11 +3815,13 @@ void cir::CoroutineOp::getSuccessorRegions(
point.getTerminatorPredecessorOrNull()->getParentRegion();
// initial_suspend either falls into body (resumed, or never actually
- // suspended because await_ready() was true) or exits directly, a plain
- // suspend here means nobody has resumed yet, so we just return to caller.
+ // suspended because await_ready() was true), exits directly, or reaches
+ // destroy when the coroutine is destroyed while suspended at its initial
+ // suspend point.
if (parent == &getInitialSuspend()) {
regions.emplace_back(&getBody());
regions.emplace_back(&getExit());
+ regions.emplace_back(&getDestroy());
return;
}
@@ -3826,7 +3836,7 @@ void cir::CoroutineOp::getSuccessorRegions(
return;
}
- // final_suspend's only live edge in valid programs is destroy resuming
+ // final_suspend's only live edge in valid programs is destroy. Resuming
// past the final suspend is UB. The ready-immediately edge to exit is
// kept for structural symmetry with the other two suspend regions even
// though it's effectively dead.
diff --git a/clang/unittests/CIR/ControlFlowTest.cpp b/clang/unittests/CIR/ControlFlowTest.cpp
index 51b22c620c0a1..236f481eef8f4 100644
--- a/clang/unittests/CIR/ControlFlowTest.cpp
+++ b/clang/unittests/CIR/ControlFlowTest.cpp
@@ -648,10 +648,12 @@ TEST_F(CIRControlFlowTest, CoroutineOp) {
RegionBranchTerminatorOpInterface initTerm =
getTerminator(coroOp.getInitialSuspend());
ASSERT_TRUE(initTerm);
- expectSuccessors(coroOp, RegionBranchPoint(initTerm),
- {&coroOp.getBody(), &coroOp.getExit()});
- expectTerminatorSuccessors(coroOp.getInitialSuspend(),
- {&coroOp.getBody(), &coroOp.getExit()});
+ expectSuccessors(
+ coroOp, RegionBranchPoint(initTerm),
+ {&coroOp.getBody(), &coroOp.getExit(), &coroOp.getDestroy()});
+ expectTerminatorSuccessors(
+ coroOp.getInitialSuspend(),
+ {&coroOp.getBody(), &coroOp.getExit(), &coroOp.getDestroy()});
// body: falls through to final_suspend, exits directly on a plain
// suspend, or reaches destroy
@@ -741,10 +743,18 @@ TEST_F(CIRControlFlowTest, AwaitOp) {
expectTerminatorSuccessors(awaitOp.getReady(),
{&awaitOp.getResume(), &awaitOp.getSuspend()});
- expectTerminatorSuccessors(awaitOp.getSuspend(), {nullptr});
+ expectTerminatorSuccessors(awaitOp.getSuspend(),
+ {&awaitOp.getResume(), nullptr});
expectTerminatorSuccessors(awaitOp.getResume(), {nullptr});
- EXPECT_FALSE(asRegionBranch(awaitOp).hasLoop());
+ // MLIR's RegionBranchOpInterface::hasLoop() reports any region reached
+ // twice as a loop, and resume is reachable both directly from ready and
+ // through suspend.
+ RegionBranchOpInterface awaitBranch = asRegionBranch(awaitOp);
+ EXPECT_FALSE(awaitBranch.isRepetitiveRegion(0));
+ EXPECT_FALSE(awaitBranch.isRepetitiveRegion(1));
+ EXPECT_FALSE(awaitBranch.isRepetitiveRegion(2));
+ EXPECT_TRUE(awaitBranch.hasLoop());
verifyControlFlowInterfaceConsistency(awaitOp);
}
More information about the cfe-commits
mailing list