llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Andres-Salamanca <details> <summary>Changes</summary> This PR implements the `FlattenCFG` pass for coroutine operations: `cir.await`, `cir.coro.body`, and `cir.co_return`. First, we introduce a new function attribute, `flatten_coroutine`. This is necessary because the existing `coroutine` attribute verifies the presence of coroutine operations such as `cir.await` and `cir.coro.body`. After `FlattenCFG` runs, these operations are eliminated, so the original attribute can no longer be used. The new attribute is preserved after flattening and is later used during LLVM lowering to emit the `presplitcoroutine` function attribute. We also introduce a new alloca attribute, `coroutine_suspend_point`. This attribute marks an alloca whose **use** represents the coroutine suspension destination. Currently, the only supported suspension destination is the return block. In the future, this mechanism can be extended to support additional destinations such as the GRO path. For `cir.await`, the pass flattens the operation by connecting its three regions: * The ready region, which determines whether execution continues through the resume or suspend path. * The resume region, which continues execution after the `cir.await`. * The suspend region, which performs the coroutine suspension sequence. The suspend path emits `llvm.coro.save` followed by `llvm.coro.suspend`. Since `llvm.coro.suspend` may return three different values: * `0` — coroutine resumed * `1` — coroutine destroyed * `-1` — coroutine suspended a `cir.switch.flat` is generated to dispatch on these results. The resume case (`0`) branches to the resume region. The suspend case (`-1`) branches to the coroutine suspension destination identified through the `coroutine_suspend_point` alloca **use**, which currently corresponds to the return block. The destroy case (`1`) creates an intermediate block containing a `cir.yield`, allowing cleanup scope flattening to correctly thread control through all enclosing cleanup scopes before eventually reaching the coroutine cleanup (`coro.free`). For `cir.coro.body`, the pass behaves similarly to `cir.scope`: the body is flattened and all `cir.co_return` operations are collected and replaced with `cir.br` operations to the continuation block corresponding to the final suspend path. --- Patch is 50.03 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/203802.diff 9 Files Affected: - (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+8-1) - (modified) clang/include/clang/CIR/MissingFeatures.h (+1) - (modified) clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp (+22-1) - (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+11-2) - (modified) clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp (+214-5) - (added) clang/test/CIR/CodeGenCoroutines/Inputs/coroutine.h (+118) - (renamed) clang/test/CIR/CodeGenCoroutines/coro-exceptions.cpp () - (added) clang/test/CIR/CodeGenCoroutines/coro-flatten.cpp (+437) - (renamed) clang/test/CIR/CodeGenCoroutines/coro-task.cpp (+9-123) ``````````diff diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index c86322b049207..5666e446a5dea 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -585,6 +585,10 @@ def CIR_AllocaOp : CIR_Op<"alloca", [ The `cleanup_dest_slot` attribute indicates that this was a temporary alloca generated by the compiler to handle cleanup exit dispatching. + The `coroutine_suspend_point` attribute indicates that this alloca is + used to track the coroutine suspend destination. The use of this alloca + identifies where control should continue when the coroutine suspends. + The result type is a pointer to the input's type. Example: @@ -605,6 +609,7 @@ def CIR_AllocaOp : CIR_Op<"alloca", [ UnitAttr:$init, UnitAttr:$constant, UnitAttr:$cleanup_dest_slot, + UnitAttr:$coroutine_suspend_point, ConfinedAttr<I64Attr, [IntMinValue<1>]>:$alignment, OptionalAttr<CIR_AnnotationArrayAttr>:$annotations ); @@ -643,7 +648,8 @@ def CIR_AllocaOp : CIR_Op<"alloca", [ `align` `(` $alignment `)` oilist( `init` $init | `const` $constant - | `cleanup_dest_slot` $cleanup_dest_slot) + | `cleanup_dest_slot` $cleanup_dest_slot + | `coroutine_suspend_point` $coroutine_suspend_point) (`size` `(` $dynAllocSize^ `)`)? `:` qualified(type($addr)) ($annotations^)? attr-dict }]; @@ -3909,6 +3915,7 @@ def CIR_FuncOp : CIR_Op<"func", [ TypeAttrOf<CIR_FuncType>:$function_type, UnitAttr:$builtin, UnitAttr:$coroutine, + UnitAttr:$flatten_coroutine, OptionalAttr<CIR_InlineKind>:$inline_kind, UnitAttr:$lambda, UnitAttr:$no_proto, diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index c09db49a955ac..3ab209fe26c6f 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -125,6 +125,7 @@ struct MissingFeatures { // Coroutines static bool coroOutsideFrameMD() { return false; } + static bool coroutineGroManager() { return false; }; // Various handling of deferred processing in CIRGenModule. static bool cgmRelease() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp index 3dd71a8ad3b6c..c2da4407cc533 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp @@ -16,7 +16,6 @@ #include "clang/AST/StmtVisitor.h" #include "clang/Basic/TargetInfo.h" #include "clang/CIR/Dialect/IR/CIRTypes.h" -#include "clang/CIR/MissingFeatures.h" using namespace clang; using namespace clang::CIRGen; @@ -49,6 +48,14 @@ struct clang::CIRGen::CGCoroData { // body must be skipped. If the promise type does not define an exception // handler, this is null. Address resumeEHVar = Address::invalid(); + + // This alloca must have a single use that represents the coroutine suspend + // destination. A cir.await operation uses this destination when the + // coroutine is suspended. + // + // Currently the suspend destination always corresponds to the return block. + // In the future it may also represent the GRO path. + cir::AllocaOp suspendPoint = nullptr; }; // Defining these here allows to keep CGCoroData private to this file. @@ -351,6 +358,13 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) { cir::CallOp coroId = emitCoroIDBuiltinCall(openCurlyLoc, nullPtrCst); createCoroData(*this, curCoro, coroId); + uint64_t alignment = cgm.getDataLayout().getAlignment(sInt32Ty, true).value(); + + auto allocaOp = cir::AllocaOp::create( + builder, openCurlyLoc, builder.getPointerTo(sInt32Ty), + "__coroutine_suspend_point", builder.getI64IntegerAttr(alignment)); + curCoro.data->suspendPoint = allocaOp; + allocaOp.setCoroutineSuspendPoint(true); // Backend is allowed to elide memory allocations, to help it, emit // auto mem = coro.alloc() ? 0 : ... allocation code ...; cir::CallOp coroAlloc = emitCoroAllocBuiltinCall(openCurlyLoc); @@ -512,6 +526,13 @@ CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) { } } + assert(!cir::MissingFeatures::coroutineGroManager()); + + cir::StoreOp::create(builder, openCurlyLoc, + builder.getSignedInt(openCurlyLoc, 1, 32), + curCoro.data->suspendPoint, false, {} /*alignment*/, + {} /*sync_scope*/, {} /*mem_order*/); + emitCoroEndBuiltinCall( openCurlyLoc, builder.getNullPtr(builder.getVoidPtrTy(), openCurlyLoc)); if (auto *ret = cast_or_null<ReturnStmt>(s.getReturnStmt())) { diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 660bed1544aac..3008b785ca00b 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -2335,6 +2335,8 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) { mlir::StringAttr builtinNameAttr = getBuiltinAttrName(state.name); mlir::StringAttr coroutineNameAttr = getCoroutineAttrName(state.name); + mlir::StringAttr flattenCoroutineNameAttr = + getFlattenCoroutineAttrName(state.name); mlir::StringAttr inlineKindNameAttr = getInlineKindAttrName(state.name); mlir::StringAttr lambdaNameAttr = getLambdaAttrName(state.name); mlir::StringAttr noProtoNameAttr = getNoProtoAttrName(state.name); @@ -2348,7 +2350,10 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) { if (::mlir::succeeded( parser.parseOptionalKeyword(coroutineNameAttr.strref()))) state.addAttribute(coroutineNameAttr, parser.getBuilder().getUnitAttr()); - + if (::mlir::succeeded( + parser.parseOptionalKeyword(flattenCoroutineNameAttr.strref()))) + state.addAttribute(flattenCoroutineNameAttr, + parser.getBuilder().getUnitAttr()); // Parse optional inline kind attribute cir::InlineKindAttr inlineKindAttr; if (failed(parseInlineKindAttr(parser, inlineKindAttr))) @@ -2657,6 +2662,9 @@ void cir::FuncOp::print(OpAsmPrinter &p) { if (getCoroutine()) p << " coroutine"; + if (getFlattenCoroutine()) + p << " flatten-coroutine"; + printInlineKindAttr(p, getInlineKindAttr()); if (getLambda()) @@ -3193,7 +3201,8 @@ cir::CoroBodyOp::getSuccessorInputs(RegionSuccessor successor) { } LogicalResult cir::CoroBodyOp::verify() { - if (!getOperation()->getParentOfType<FuncOp>().getCoroutine()) + auto funcOp = getOperation()->getParentOfType<FuncOp>(); + if (!funcOp.getCoroutine() && !funcOp.getFlattenCoroutine()) return emitOpError("enclosing function must be a coroutine"); return success(); } diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp index ddeeb98fee820..2823da3e1e33e 100644 --- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp +++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp @@ -835,7 +835,8 @@ class CIRCleanupScopeOpFlattening } else if (isa<cir::LoopOpInterface>(nestedOp)) { collectExitsInLoop(nestedOp); return mlir::WalkResult::skip(); - } else if (isa<cir::ReturnOp, cir::ContinueOp>(nestedOp)) { + } else if (isa<cir::CoReturnOp, cir::ReturnOp, cir::ContinueOp>( + nestedOp)) { exits.emplace_back(nestedOp, nextId++); } else if (isGotoThatExitsCleanup(nestedOp)) { exits.emplace_back(nestedOp, nextId++); @@ -857,7 +858,7 @@ class CIRCleanupScopeOpFlattening // the nested cleanup. if (!ignoreBreak && isa<cir::BreakOp>(op)) { exits.emplace_back(op, nextId++); - } else if (isa<cir::ContinueOp, cir::ReturnOp>(op)) { + } else if (isa<cir::CoReturnOp, cir::ContinueOp, cir::ReturnOp>(op)) { exits.emplace_back(op, nextId++); } else if (isGotoThatExitsCleanup(op)) { exits.emplace_back(op, nextId++); @@ -1010,6 +1011,13 @@ class CIRCleanupScopeOpFlattening cir::ContinueOp::create(rewriter, loc); return mlir::success(); }) + .Case<cir::CoReturnOp>([&](auto) { + // CoReturnOp does not carry a destination operand. The continuation + // block determines whether execution proceeds to another cleanup or + // to the coroutine's final suspend path. + cir::CoReturnOp::create(rewriter, loc); + return mlir::success(); + }) .Case<cir::ReturnOp>([&](auto returnOp) { // Return from the cleanup exit. Note, if this is a return inside a // nested cleanup scope, the flattening of the outer scope will handle @@ -1805,11 +1813,212 @@ class CIRTryOpFlattening : public mlir::OpRewritePattern<cir::TryOp> { } }; +static mlir::Block *getOrCreateBlockForSuspendPoint( + cir::FuncOp funcOp, mlir::PatternRewriter &rewriter, mlir::Location loc) { + mlir::Block &entryBlock = funcOp.getBody().front(); + + auto it = llvm::find_if(entryBlock, [](auto &op) { + return mlir::isa<AllocaOp>(&op) && + mlir::cast<AllocaOp>(&op).getCoroutineSuspendPoint(); + }); + + assert(it->hasOneUse() && + "coroutine suspend point alloca must have exactly one use"); + auto storeOp = cast<cir::StoreOp>(*it->getUses().begin()->getOwner()); + auto suspendPoint = cast<cir::ConstantOp>(storeOp.getValue().getDefiningOp()); + mlir::Block *suspendBlock = suspendPoint->getBlock(); + if (&suspendBlock->front() == suspendPoint) + return suspendBlock; + + mlir::OpBuilder::InsertionGuard guard(rewriter); + mlir::Block *remainingBlock = + rewriter.splitBlock(suspendBlock, suspendPoint->getIterator()); + rewriter.setInsertionPointToEnd(suspendBlock); + cir::BrOp::create(rewriter, loc, remainingBlock); + return remainingBlock; +} + +class CIRAwaitOpFlattening : public mlir::OpRewritePattern<cir::AwaitOp> { +public: + using OpRewritePattern<cir::AwaitOp>::OpRewritePattern; + + mlir::LogicalResult + matchAndRewrite(cir::AwaitOp awaitOp, + mlir::PatternRewriter &rewriter) const override { + mlir::Block *awaitBlock = rewriter.getInsertionBlock(); + mlir::Block *remainingOpsBlock = + rewriter.splitBlock(awaitBlock, rewriter.getInsertionPoint()); + + mlir::Location loc = awaitOp.getLoc(); + + mlir::Region &readyRegion = awaitOp.getReady(); + mlir::Block &beforeReady = awaitOp.getReady().front(); + mlir::Region &suspendRegion = awaitOp.getSuspend(); + mlir::Region &resumeRegion = awaitOp.getResume(); + auto conditionOp = + cast<cir::ConditionOp>(readyRegion.back().getTerminator()); + { + mlir::OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(conditionOp); + rewriter.replaceOpWithNewOp<cir::BrCondOp>( + conditionOp, conditionOp.getCondition(), &resumeRegion.front(), + &suspendRegion.front()); + } + rewriter.inlineRegionBefore(readyRegion, remainingOpsBlock); + + { + mlir::OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToEnd(awaitBlock); + cir::BrOp::create(rewriter, loc, mlir::ValueRange(), &beforeReady); + } + + auto suspendYield = + cast<cir::YieldOp>(suspendRegion.back().getTerminator()); + cir::LLVMIntrinsicCallOp coroSuspendIntri = nullptr; + { + mlir::OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(&suspendRegion.front().front()); + + // Insert coro.save at the beginning of the suspend region. + // This captures the current coroutine state before suspension. + auto voidPtrTy = cir::PointerType::get(cir::VoidType::get(getContext())); + auto nullPtr = cir::ConstantOp::create( + rewriter, loc, + cir::ConstPtrAttr::get(voidPtrTy, rewriter.getI64IntegerAttr(0))); + auto coroSaveIntri = cir::LLVMIntrinsicCallOp::create( + rewriter, loc, mlir::StringAttr::get(getContext(), "llvm.coro.save"), + cir::IntType::get(getContext(), 32, false), + mlir::ValueRange{nullPtr}); + rewriter.setInsertionPoint(suspendYield); + + bool isFinalSuspend = awaitOp.getKind() == cir::AwaitKind::Final; + auto isFinalCoroSuspend = cir::ConstantOp::create( + rewriter, loc, cir::BoolAttr::get(getContext(), isFinalSuspend)); + + // llvm.coro.suspend returns: + // -1 : coroutine suspended + // 0 : coroutine resumed + // 1 : coroutine destroyed + coroSuspendIntri = cir::LLVMIntrinsicCallOp::create( + rewriter, loc, + mlir::StringAttr::get(getContext(), "llvm.coro.suspend"), + cir::IntType::get(getContext(), 32, false), + mlir::ValueRange{coroSaveIntri.getResult(), isFinalCoroSuspend}); + } + rewriter.inlineRegionBefore(suspendRegion, remainingOpsBlock); + + auto func = awaitOp->getParentOfType<cir::FuncOp>(); + + { + mlir::OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(suspendYield); + llvm::SmallVector<mlir::APInt, 2> caseValues{mlir::APInt(32, 0), + mlir::APInt(32, 1)}; + + llvm::SmallVector<mlir::ValueRange, 8> caseOperands{ + mlir::ValueRange(), mlir::ValueRange(), mlir::ValueRange()}; + + llvm::SmallVector<mlir::Block *, 8> caseDestinations; + + // In Classic CodeGen, the destroy path reaches the coroutine cleanup by + // emitting an EmitBranchThroughCleanup(), ensuring that all nested + // cleanup scopes are executed before control reaches the coro.free + // cleanup. + // + // We achieve the same effect by creating a block that only contains a + // cir.yield. createExitTerminator() then propagates control through every + // enclosing cleanup scope until the parent coroutine cleanup (coro.free) + // is reached, after which execution continues to the return block. + mlir::Block *cleanupBlock = nullptr; + { + mlir::OpBuilder::InsertionGuard guard(rewriter); + cleanupBlock = rewriter.createBlock(remainingOpsBlock); + cir::YieldOp::create(rewriter, loc); + } + caseDestinations.push_back(&resumeRegion.front()); + caseDestinations.push_back(cleanupBlock); + + assert(!cir::MissingFeatures::coroutineGroManager()); + + // Default destination must be de suspend BB (the return block or the pre + // gro conv) + auto coroSuspendSwitch = cir::SwitchFlatOp::create( + rewriter, loc, coroSuspendIntri.getResult(), + getOrCreateBlockForSuspendPoint(func, rewriter, loc), + mlir::ValueRange(), caseValues, caseDestinations, caseOperands); + + rewriter.replaceOp(suspendYield, coroSuspendSwitch); + } + + auto resumeYield = cast<cir::YieldOp>(resumeRegion.back().getTerminator()); + { + mlir::OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPoint(resumeYield); + rewriter.replaceOpWithNewOp<cir::BrOp>(resumeYield, remainingOpsBlock); + } + rewriter.inlineRegionBefore(resumeRegion, remainingOpsBlock); + + rewriter.eraseOp(awaitOp); + + func.setCoroutine(false); + func.setFlattenCoroutine(true); + + return mlir::success(); + } +}; + +class CIRCoroBodyOpFlattening : public mlir::OpRewritePattern<cir::CoroBodyOp> { +public: + using OpRewritePattern<cir::CoroBodyOp>::OpRewritePattern; + + mlir::LogicalResult + matchAndRewrite(cir::CoroBodyOp coroBodyOp, + mlir::PatternRewriter &rewriter) const override { + + if (hasNestedOpsToFlatten(coroBodyOp.getBody())) + return mlir::failure(); + + llvm::SmallVector<cir::CoReturnOp> coReturns; + coroBodyOp.getBody().walk<mlir::WalkOrder::PreOrder>( + [&](cir::CoReturnOp op) { + coReturns.push_back(op); + return mlir::WalkResult::advance(); + }); + + mlir::OpBuilder::InsertionGuard guard(rewriter); + mlir::Location loc = coroBodyOp.getLoc(); + + mlir::Block *currentBlock = rewriter.getInsertionBlock(); + mlir::Block *continueBlock = + rewriter.splitBlock(currentBlock, rewriter.getInsertionPoint()); + + // Inline body region. + mlir::Block *beforeBody = &coroBodyOp.getBody().front(); + rewriter.inlineRegionBefore(coroBodyOp.getBody(), continueBlock); + + rewriter.setInsertionPointToEnd(currentBlock); + cir::BrOp::create(rewriter, loc, mlir::ValueRange(), beforeBody); + + // In CIR CodeGen, the operation following CoroBodyOp is always the + // final-suspend path. Therefore, the continuation block created by the + // split corresponds to the final suspend point. + for (cir::CoReturnOp &coReturn : coReturns) { + rewriter.setInsertionPoint(coReturn); + rewriter.replaceOpWithNewOp<cir::BrOp>(coReturn, continueBlock); + } + + rewriter.replaceOp(coroBodyOp, continueBlock->getArguments()); + + return mlir::success(); + } +}; + void populateFlattenCFGPatterns(RewritePatternSet &patterns) { patterns .add<CIRIfFlattening, CIRLoopOpInterfaceFlattening, CIRScopeOpFlattening, CIRSwitchOpFlattening, CIRTernaryOpFlattening, - CIRCleanupScopeOpFlattening, CIRTryOpFlattening>( + CIRCleanupScopeOpFlattening, CIRTryOpFlattening, + CIRAwaitOpFlattening, CIRCoroBodyOpFlattening>( patterns.getContext()); } @@ -1820,8 +2029,8 @@ void CIRFlattenCFGPass::runOnOperation() { // Collect operations to apply patterns. llvm::SmallVector<Operation *, 16> ops; getOperation()->walk<mlir::WalkOrder::PostOrder>([&](Operation *op) { - if (isa<IfOp, ScopeOp, SwitchOp, LoopOpInterface, TernaryOp, CleanupScopeOp, - TryOp>(op)) + if (isa<AwaitOp, CoroBodyOp, IfOp, ScopeOp, SwitchOp, LoopOpInterface, + TernaryOp, CleanupScopeOp, TryOp>(op)) ops.push_back(op); }); diff --git a/clang/test/CIR/CodeGenCoroutines/Inputs/coroutine.h b/clang/test/CIR/CodeGenCoroutines/Inputs/coroutine.h new file mode 100644 index 0000000000000..1c0cee22af2fc --- /dev/null +++ b/clang/test/CIR/CodeGenCoroutines/Inputs/coroutine.h @@ -0,0 +1,118 @@ + +namespace std { + +template<typename T> struct remove_reference { typedef T type; }; +template<typename T> struct remove_reference<T &> { typedef T type; }; +template<typename T> struct remove_reference<T &&> { typedef T type; }; + +template<typename T> +typename remove_reference<T>::type &&move(T &&t) noexcept; + +template <class Ret, typename... T> +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template <class Promise = void> +struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; +}; +template <> +struct coroutine_handle<void> { + template <class PromiseType> + coroutine_handle(coroutine_handle<PromiseType>) noexcept; + static coroutine_handle from_address(void *); +}; + +struct suspend_always { + bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +struct suspend_never { + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +struct string { + int size() const; + string(); + string(char const *s); +}; + +template<typename T> +struct optional { + optional(); + optional(const T&); + T &operator*() &; + T &&operator*() &&; + T &value() &; + T &&value() &&; +}; +} // namespace std + +namespace folly { +namespace coro { + +using std::suspend_always; +using std::suspend_never; +using std::coroutine_handle; + +using SemiFuture = int; + +template<class T> +struct Task { + struct promise_type { + Task<T> get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_value(T); + void unhandled_exception(); + auto yield_value(Task<T>) noexcept { return final_suspend(); } + }; + bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept {} + T await_resume(); +}; + +template<> +struct Task<void> { + struct promise_type { + Task<void> get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_alway... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/203802 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
