================
@@ -492,54 +431,142 @@ CIRGenFunction::emitCoroutineBody(const
CoroutineBodyStmt &s) {
.failed()) {
return mlir::failure();
}
- }
- mlir::Block &coroBodyBlock = coroBodyOp.getBody().back();
- if (!coroBodyBlock.mightHaveTerminator()) {
- mlir::OpBuilder::InsertionGuard guard(builder);
- builder.setInsertionPointToEnd(&coroBodyBlock);
+ return mlir::success();
+ };
+
+ // Builds `final_suspend`: only emitted at all if the body can actually
+ // reach it (an explicit co_return, or falling off the end).
+ auto finalSuspendBuilder = [&]() -> mlir::LogicalResult {
+ // Note that LLVM checks CanFallthrough by looking into the availability
+ // of the insert block which is kinda brittle and unintuitive, seems to
be
+ // related with how landing pads are handled.
+ //
+ // CIRGen handles this by checking pre-existing co_returns in the current
+ // scope instead.
+ //
+ // From LLVM IR Gen: const bool CanFallthrough =
Builder.GetInsertBlock();
+ const bool canFallthrough = curLexScope->hasCoreturn();
+ const bool hasCoreturns = curCoro.data->coreturnCount > 0;
+ if (canFallthrough || hasCoreturns) {
+ curCoro.data->currentAwaitKind = cir::AwaitKind::Final;
+ if (emitStmt(s.getFinalSuspendStmt(), /*useCurrentScope=*/true)
+ .failed())
+ return mlir::failure();
+ }
cir::YieldOp::create(builder, openCurlyLoc);
- }
+ return mlir::success();
+ };
+
+ // Emit "if (coro.free(CoroId, CoroBegin)) Deallocate;"
+ auto destroyBuilder = [&]() -> mlir::LogicalResult {
+ Stmt *deallocate = s.getDeallocate();
+ if (emitStmt(deallocate, /*useCurrentScope=*/true).failed()) {
+ cgm.error(deallocate->getBeginLoc(),
+ "failed to emit coroutine deallocation expression");
+ return mlir::failure();
+ }
+
+ cir::CoroFreeOp coroFree = curCoro.data->lastCoroFree;
- // Note that LLVM checks CanFallthrough by looking into the availability
- // of the insert block which is kinda brittle and unintuitive, seems to be
- // related with how landing pads are handled.
- //
- // CIRGen handles this by checking pre-existing co_returns in the current
- // scope instead.
- //
- // From LLVM IR Gen: const bool CanFallthrough = Builder.GetInsertBlock();
- const bool canFallthrough = curLexScope->hasCoreturn();
- const bool hasCoreturns = curCoro.data->coreturnCount > 0;
- if (canFallthrough || hasCoreturns) {
- curCoro.data->currentAwaitKind = cir::AwaitKind::Final;
+ if (!coroFree) {
+ cgm.error(deallocate->getBeginLoc(),
+ "Deallocation expression does not refer to coro.free");
+ return mlir::failure();
+ }
{
mlir::OpBuilder::InsertionGuard guard(builder);
- if (emitStmt(s.getFinalSuspendStmt(), /*useCurrentScope=*/true)
- .failed())
+ builder.setInsertionPointAfter(coroFree);
+ mlir::Value isPtrNotNull =
+ builder.createPtrIsNotNull(coroFree.getResult());
+
+ llvm::SmallVector<mlir::Operation *> opsToMove;
+ mlir::Block *block = builder.getInsertionBlock();
+ mlir::Block::iterator it(isPtrNotNull.getDefiningOp());
+
+ for (++it; it != block->end(); ++it)
+ opsToMove.push_back(&*it);
+
+ auto ifOp = cir::IfOp::create(
+ builder, getLoc(deallocate->getSourceRange()), isPtrNotNull,
+ /*withElseRegion*/ false,
+ [&](mlir::OpBuilder &builder, mlir::Location loc) {
+ cir::YieldOp::create(builder, loc);
+ });
+
+ mlir::Operation *yieldOp = ifOp.getThenRegion().back().getTerminator();
+ for (auto *op : opsToMove)
+ op->moveBefore(yieldOp);
+ }
+
+ cir::YieldOp::create(builder, openCurlyLoc);
+ return mlir::success();
+ };
+
+ // Builds `exit`: coro.end(/*unwind*/ false) followed by the actual return
+ // to the caller.
+ auto exitBuilder = [&]() {
+ cir::ConstantOp nullHandler =
+ builder.getNullPtr(builder.getVoidPtrTy(), openCurlyLoc);
+ cir::ConstantOp noUnwind = builder.getBool(false, openCurlyLoc);
+ auto tkNone = cir::TokenNoneOp::create(builder, openCurlyLoc);
+ cir::CoroEndOp::create(builder, openCurlyLoc, nullHandler, noUnwind,
+ tkNone);
+
+ if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) {
----------------
Andres-Salamanca wrote:
Haha, it was a bit difficult to find a case where this happens, but you are
correct. The following example causes `ReturnStmt` to be `nullptr`:
```cpp
namespace std {
template <typename... Args>
struct coroutine_traits<void, Args...> {
struct promise_type {
void get_return_object() {}
std::suspend_never initial_suspend() noexcept {
return {};
}
std::suspend_never final_suspend() noexcept {
return {};
}
void return_void() noexcept {}
void unhandled_exception() {}
};
};
}
void foo() {
co_await std::suspend_never{};
}
```
This is a special case in Sema:
https://github.com/llvm/llvm-project/blob/a2fdb8fe0d09dbc1d543c3381f0ddacdf7054c5e/clang/lib/Sema/SemaCoroutine.cpp#L1901-L1910
In this case, the `return` statement in CIR ends up outside the
`cir.coroutine`, so the `exit` region is left without a terminator.
The original LLVM IR still terminates the function with a `ret void`:
```llvm
coro.ret:
call void @llvm.coro.end(ptr null, i1 false, token none)
ret void
```
I think it would be correct to emit a `cir.return` inside the `exit` region
when this `nullptr` case happens.
https://github.com/llvm/llvm-project/pull/213191
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits