| //===----- CGCoroutine.cpp - Emit CIR Code for C++ coroutines -------------===// |
| // |
| // 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 |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This contains code dealing with C++ code generation of coroutines. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "CIRGenFunction.h" |
| #include "mlir/Support/LLVM.h" |
| #include "clang/AST/StmtCXX.h" |
| #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; |
| |
| struct clang::CIRGen::CGCoroData { |
| // What is the current await expression kind and how many |
| // await/yield expressions were encountered so far. |
| // These are used to generate pretty labels for await expressions in LLVM IR. |
| cir::AwaitKind currentAwaitKind = cir::AwaitKind::Init; |
| // Stores the __builtin_coro_id emitted in the function so that we can supply |
| // it as the first argument to other builtins. |
| cir::CallOp coroId = nullptr; |
| |
| // Stores the result of __builtin_coro_begin call. |
| mlir::Value coroBegin = nullptr; |
| |
| // Stores the insertion point for final suspend, this happens after the |
| // promise call (return_xxx promise member) but before a cir.br to the return |
| // block. |
| mlir::Operation *finalSuspendInsPoint; |
| |
| // How many co_return statements are in the coroutine. Used to decide whether |
| // we need to add co_return; equivalent at the end of the user authored body. |
| unsigned coreturnCount = 0; |
| |
| // The promise type's 'unhandled_exception' handler, if it defines one. |
| Stmt *exceptionHandler = nullptr; |
| }; |
| |
| // Defining these here allows to keep CGCoroData private to this file. |
| CIRGenFunction::CGCoroInfo::CGCoroInfo() {} |
| CIRGenFunction::CGCoroInfo::~CGCoroInfo() {} |
| |
| namespace { |
| // FIXME: both GetParamRef and ParamReferenceReplacerRAII are good template |
| // candidates to be shared among LLVM / CIR codegen. |
| |
| // Hunts for the parameter reference in the parameter copy/move declaration. |
| struct GetParamRef : public StmtVisitor<GetParamRef> { |
| public: |
| DeclRefExpr *expr = nullptr; |
| GetParamRef() {} |
| void VisitDeclRefExpr(DeclRefExpr *e) { |
| assert(expr == nullptr && "multilple declref in param move"); |
| expr = e; |
| } |
| void VisitStmt(Stmt *s) { |
| for (Stmt *c : s->children()) { |
| if (c) |
| Visit(c); |
| } |
| } |
| }; |
| |
| // This class replaces references to parameters to their copies by changing |
| // the addresses in CGF.LocalDeclMap and restoring back the original values in |
| // its destructor. |
| struct ParamReferenceReplacerRAII { |
| CIRGenFunction::DeclMapTy savedLocals; |
| CIRGenFunction::DeclMapTy &localDeclMap; |
| |
| ParamReferenceReplacerRAII(CIRGenFunction::DeclMapTy &localDeclMap) |
| : localDeclMap(localDeclMap) {} |
| |
| void addCopy(const DeclStmt *pm) { |
| // Figure out what param it refers to. |
| |
| assert(pm->isSingleDecl()); |
| const VarDecl *vd = static_cast<const VarDecl *>(pm->getSingleDecl()); |
| const Expr *initExpr = vd->getInit(); |
| GetParamRef visitor; |
| visitor.Visit(const_cast<Expr *>(initExpr)); |
| assert(visitor.expr); |
| DeclRefExpr *dreOrig = visitor.expr; |
| auto *pd = dreOrig->getDecl(); |
| |
| auto it = localDeclMap.find(pd); |
| assert(it != localDeclMap.end() && "parameter is not found"); |
| savedLocals.insert({pd, it->second}); |
| |
| auto copyIt = localDeclMap.find(vd); |
| assert(copyIt != localDeclMap.end() && "parameter copy is not found"); |
| it->second = copyIt->getSecond(); |
| } |
| |
| ~ParamReferenceReplacerRAII() { |
| for (auto &&savedLocal : savedLocals) { |
| localDeclMap.insert({savedLocal.first, savedLocal.second}); |
| } |
| } |
| }; |
| } // namespace |
| |
| RValue CIRGenFunction::emitCoroutineFrame() { |
| if (curCoro.data && curCoro.data->coroBegin) { |
| return RValue::get(curCoro.data->coroBegin); |
| } |
| cgm.errorNYI("NYI"); |
| return RValue(); |
| } |
| |
| static void createCoroData(CIRGenFunction &cgf, |
| CIRGenFunction::CGCoroInfo &curCoro, |
| cir::CallOp coroId) { |
| assert(!curCoro.data && "EmitCoroutineBodyStatement called twice?"); |
| |
| curCoro.data = std::make_unique<CGCoroData>(); |
| curCoro.data->coroId = coroId; |
| } |
| |
| static mlir::LogicalResult |
| emitBodyAndFallthrough(CIRGenFunction &cgf, const CoroutineBodyStmt &s, |
| Stmt *body, |
| const CIRGenFunction::LexicalScope *currLexScope) { |
| if (cgf.emitStmt(body, /*useCurrentScope=*/true).failed()) |
| return mlir::failure(); |
| // Note that classic codegen 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 = !currLexScope->hasCoreturn(); |
| if (canFallthrough) |
| if (Stmt *onFallthrough = s.getFallthroughHandler()) |
| if (cgf.emitStmt(onFallthrough, /*useCurrentScope=*/true).failed()) |
| return mlir::failure(); |
| |
| return mlir::success(); |
| } |
| |
| cir::CallOp CIRGenFunction::emitCoroIDBuiltinCall(mlir::Location loc, |
| mlir::Value nullPtr) { |
| cir::IntType int32Ty = builder.getUInt32Ty(); |
| |
| const TargetInfo &ti = cgm.getASTContext().getTargetInfo(); |
| unsigned newAlign = ti.getNewAlign() / ti.getCharWidth(); |
| |
| mlir::Operation *builtin = cgm.getGlobalValue(cgm.builtinCoroId); |
| |
| cir::FuncOp fnOp; |
| if (!builtin) { |
| fnOp = cgm.createCIRBuiltinFunction( |
| loc, cgm.builtinCoroId, |
| cir::FuncType::get({int32Ty, voidPtrTy, voidPtrTy, voidPtrTy}, int32Ty), |
| /*FD=*/nullptr); |
| assert(fnOp && "should always succeed"); |
| } else { |
| fnOp = cast<cir::FuncOp>(builtin); |
| } |
| |
| return builder.createCallOp(loc, fnOp, |
| mlir::ValueRange{builder.getUInt32(newAlign, loc), |
| nullPtr, nullPtr, nullPtr}); |
| } |
| |
| cir::CallOp CIRGenFunction::emitCoroAllocBuiltinCall(mlir::Location loc) { |
| cir::BoolType boolTy = builder.getBoolTy(); |
| |
| mlir::Operation *builtin = cgm.getGlobalValue(cgm.builtinCoroAlloc); |
| |
| cir::FuncOp fnOp; |
| if (!builtin) { |
| fnOp = cgm.createCIRBuiltinFunction(loc, cgm.builtinCoroAlloc, |
| cir::FuncType::get({uInt32Ty}, boolTy), |
| /*fd=*/nullptr); |
| assert(fnOp && "should always succeed"); |
| } else { |
| fnOp = cast<cir::FuncOp>(builtin); |
| } |
| |
| return builder.createCallOp( |
| loc, fnOp, mlir::ValueRange{curCoro.data->coroId.getResult()}); |
| } |
| |
| cir::CallOp |
| CIRGenFunction::emitCoroBeginBuiltinCall(mlir::Location loc, |
| mlir::Value coroframeAddr) { |
| mlir::Operation *builtin = cgm.getGlobalValue(cgm.builtinCoroBegin); |
| |
| cir::FuncOp fnOp; |
| if (!builtin) { |
| fnOp = cgm.createCIRBuiltinFunction( |
| loc, cgm.builtinCoroBegin, |
| cir::FuncType::get({uInt32Ty, voidPtrTy}, voidPtrTy), |
| /*fd=*/nullptr); |
| assert(fnOp && "should always succeed"); |
| } else { |
| fnOp = cast<cir::FuncOp>(builtin); |
| } |
| |
| return builder.createCallOp( |
| loc, fnOp, |
| mlir::ValueRange{curCoro.data->coroId.getResult(), coroframeAddr}); |
| } |
| |
| mlir::LogicalResult |
| CIRGenFunction::emitCoroutineBody(const CoroutineBodyStmt &s) { |
| mlir::Location openCurlyLoc = getLoc(s.getBeginLoc()); |
| cir::ConstantOp nullPtrCst = builder.getNullPtr(voidPtrTy, openCurlyLoc); |
| |
| auto fn = mlir::cast<cir::FuncOp>(curFn); |
| fn.setCoroutine(true); |
| cir::CallOp coroId = emitCoroIDBuiltinCall(openCurlyLoc, nullPtrCst); |
| createCoroData(*this, curCoro, coroId); |
| |
| // Backend is allowed to elide memory allocations, to help it, emit |
| // auto mem = coro.alloc() ? 0 : ... allocation code ...; |
| cir::CallOp coroAlloc = emitCoroAllocBuiltinCall(openCurlyLoc); |
| |
| // Initialize address of coroutine frame to null |
| CanQualType astVoidPtrTy = cgm.getASTContext().VoidPtrTy; |
| mlir::Type allocaTy = convertTypeForMem(astVoidPtrTy); |
| Address coroFrame = |
| createTempAlloca(allocaTy, getContext().getTypeAlignInChars(astVoidPtrTy), |
| openCurlyLoc, "__coro_frame_addr", |
| /*ArraySize=*/nullptr); |
| |
| mlir::Value storeAddr = coroFrame.getPointer(); |
| builder.CIRBaseBuilderTy::createStore(openCurlyLoc, nullPtrCst, storeAddr); |
| cir::IfOp::create( |
| builder, openCurlyLoc, coroAlloc.getResult(), |
| /*withElseRegion=*/false, |
| /*thenBuilder=*/[&](mlir::OpBuilder &b, mlir::Location loc) { |
| builder.CIRBaseBuilderTy::createStore( |
| loc, emitScalarExpr(s.getAllocate()), storeAddr); |
| cir::YieldOp::create(builder, loc); |
| }); |
| curCoro.data->coroBegin = |
| emitCoroBeginBuiltinCall( |
| openCurlyLoc, |
| cir::LoadOp::create(builder, openCurlyLoc, allocaTy, storeAddr)) |
| .getResult(); |
| |
| // Handle allocation failure if 'ReturnStmtOnAllocFailure' was provided. |
| if (s.getReturnStmtOnAllocFailure()) |
| cgm.errorNYI("handle coroutine return alloc failure"); |
| |
| { |
| assert(!cir::MissingFeatures::generateDebugInfo()); |
| ParamReferenceReplacerRAII paramReplacer(localDeclMap); |
| // Create mapping between parameters and copy-params for coroutine |
| // function. |
| llvm::ArrayRef<const Stmt *> paramMoves = s.getParamMoves(); |
| assert((paramMoves.size() == 0 || (paramMoves.size() == fnArgs.size())) && |
| "ParamMoves and FnArgs should be the same size for coroutine " |
| "function"); |
| // For zipping the arg map into debug info. |
| assert(!cir::MissingFeatures::generateDebugInfo()); |
| |
| // Create parameter copies. We do it before creating a promise, since an |
| // evolution of coroutine TS may allow promise constructor to observe |
| // parameter copies. |
| assert(!cir::MissingFeatures::coroOutsideFrameMD()); |
| for (auto *pm : paramMoves) { |
| if (emitStmt(pm, /*useCurrentScope=*/true).failed()) |
| return mlir::failure(); |
| paramReplacer.addCopy(cast<DeclStmt>(pm)); |
| } |
| |
| if (emitStmt(s.getPromiseDeclStmt(), /*useCurrentScope=*/true).failed()) |
| return mlir::failure(); |
| // returnValue should be valid as long as the coroutine's return type |
| // is not void. The assertion could help us to reduce the check later. |
| assert(returnValue.isValid() == (bool)s.getReturnStmt()); |
| // Now we have the promise, initialize the GRO. |
| // We need to emit `get_return_object` first. According to: |
| // [dcl.fct.def.coroutine]p7 |
| // The call to get_return_Âobject is sequenced before the call to |
| // initial_suspend and is invoked at most once. |
| // |
| // So we couldn't emit return value when we emit return statment, |
| // otherwise the call to get_return_object wouldn't be in front |
| // of initial_suspend. |
| if (returnValue.isValid()) |
| emitAnyExprToMem(s.getReturnValue(), returnValue, |
| s.getReturnValue()->getType().getQualifiers(), |
| /*isInit*/ true); |
| |
| assert(!cir::MissingFeatures::ehCleanupScope()); |
| |
| curCoro.data->currentAwaitKind = cir::AwaitKind::Init; |
| if (emitStmt(s.getInitSuspendStmt(), /*useCurrentScope=*/true).failed()) |
| return mlir::failure(); |
| |
| curCoro.data->currentAwaitKind = cir::AwaitKind::User; |
| |
| // FIXME(cir): wrap emitBodyAndFallthrough with try/catch bits. |
| if (s.getExceptionHandler()) |
| assert(!cir::MissingFeatures::coroutineExceptions()); |
| if (emitBodyAndFallthrough(*this, s, s.getBody(), curLexScope).failed()) |
| return mlir::failure(); |
| |
| // 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; |
| { |
| mlir::OpBuilder::InsertionGuard guard(builder); |
| builder.setInsertionPoint(curCoro.data->finalSuspendInsPoint); |
| if (emitStmt(s.getFinalSuspendStmt(), /*useCurrentScope=*/true) |
| .failed()) |
| return mlir::failure(); |
| } |
| } |
| } |
| return mlir::success(); |
| } |
| |
| static bool memberCallExpressionCanThrow(const Expr *e) { |
| if (const auto *ce = dyn_cast<CXXMemberCallExpr>(e)) |
| if (const auto *proto = |
| ce->getMethodDecl()->getType()->getAs<FunctionProtoType>()) |
| if (isNoexceptExceptionSpec(proto->getExceptionSpecType()) && |
| proto->canThrow() == CT_Cannot) |
| return false; |
| return true; |
| } |
| |
| // Given a suspend expression which roughly looks like: |
| // |
| // auto && x = CommonExpr(); |
| // if (!x.await_ready()) { |
| // x.await_suspend(...); (*) |
| // } |
| // x.await_resume(); |
| // |
| // where the result of the entire expression is the result of x.await_resume() |
| // |
| // (*) If x.await_suspend return type is bool, it allows to veto a suspend: |
| // if (x.await_suspend(...)) |
| // llvm_coro_suspend(); |
| // |
| // This is more higher level than LLVM codegen, for that one see llvm's |
| // docs/Coroutines.rst for more details. |
| namespace { |
| struct LValueOrRValue { |
| LValue lv; |
| RValue rv; |
| }; |
| } // namespace |
| |
| static LValueOrRValue |
| emitSuspendExpression(CIRGenFunction &cgf, CGCoroData &coro, |
| CoroutineSuspendExpr const &s, cir::AwaitKind kind, |
| AggValueSlot aggSlot, bool ignoreResult, |
| mlir::Block *scopeParentBlock, |
| mlir::Value &tmpResumeRValAddr, bool forLValue) { |
| [[maybe_unused]] mlir::LogicalResult awaitBuild = mlir::success(); |
| LValueOrRValue awaitRes; |
| |
| CIRGenFunction::OpaqueValueMapping binder = |
| CIRGenFunction::OpaqueValueMapping(cgf, s.getOpaqueValue()); |
| CIRGenBuilderTy &builder = cgf.getBuilder(); |
| [[maybe_unused]] cir::AwaitOp awaitOp = cir::AwaitOp::create( |
| builder, cgf.getLoc(s.getSourceRange()), kind, |
| /*readyBuilder=*/ |
| [&](mlir::OpBuilder &b, mlir::Location loc) { |
| Expr *condExpr = s.getReadyExpr()->IgnoreParens(); |
| builder.createCondition(cgf.evaluateExprAsBool(condExpr)); |
| }, |
| /*suspendBuilder=*/ |
| [&](mlir::OpBuilder &b, mlir::Location loc) { |
| // Note that differently from LLVM codegen we do not emit coro.save |
| // and coro.suspend here, that should be done as part of lowering this |
| // to LLVM dialect (or some other MLIR dialect) |
| |
| // A invalid suspendRet indicates "void returning await_suspend" |
| mlir::Value suspendRet = cgf.emitScalarExpr(s.getSuspendExpr()); |
| |
| // Veto suspension if requested by bool returning await_suspend. |
| if (suspendRet) { |
| cgf.cgm.errorNYI("Veto await_suspend"); |
| } |
| |
| // Signals the parent that execution flows to next region. |
| cir::YieldOp::create(builder, loc); |
| }, |
| /*resumeBuilder=*/ |
| [&](mlir::OpBuilder &b, mlir::Location loc) { |
| // Exception handling requires additional IR. If the 'await_resume' |
| // function is marked as 'noexcept', we avoid generating this additional |
| // IR. |
| CXXTryStmt *tryStmt = nullptr; |
| if (coro.exceptionHandler && kind == cir::AwaitKind::Init && |
| memberCallExpressionCanThrow(s.getResumeExpr())) |
| cgf.cgm.errorNYI("Coro resume Exception"); |
| |
| // FIXME(cir): the alloca for the resume expr should be placed in the |
| // enclosing cir.scope instead. |
| if (forLValue) { |
| assert(!cir::MissingFeatures::coroCoYield()); |
| } else { |
| awaitRes.rv = |
| cgf.emitAnyExpr(s.getResumeExpr(), aggSlot, ignoreResult); |
| if (!awaitRes.rv.isIgnored()) |
| // Create the alloca in the block before the scope wrapping |
| // cir.await. |
| assert(!cir::MissingFeatures::coroCoReturn()); |
| } |
| |
| if (tryStmt) |
| cgf.cgm.errorNYI("Coro tryStmt"); |
| |
| // Returns control back to parent. |
| cir::YieldOp::create(builder, loc); |
| }); |
| |
| assert(awaitBuild.succeeded() && "Should know how to codegen"); |
| return awaitRes; |
| } |
| |
| static RValue emitSuspendExpr(CIRGenFunction &cgf, |
| const CoroutineSuspendExpr &e, |
| cir::AwaitKind kind, AggValueSlot aggSlot, |
| bool ignoreResult) { |
| RValue rval; |
| mlir::Location scopeLoc = cgf.getLoc(e.getSourceRange()); |
| |
| // Since we model suspend / resume as an inner region, we must store |
| // resume scalar results in a tmp alloca, and load it after we build the |
| // suspend expression. An alternative way to do this would be to make |
| // every region return a value when promise.return_value() is used, but |
| // it's a bit awkward given that resume is the only region that actually |
| // returns a value. |
| mlir::Block *currEntryBlock = cgf.curLexScope->getEntryBlock(); |
| [[maybe_unused]] mlir::Value tmpResumeRValAddr; |
| |
| // No need to explicitly wrap this into a scope since the AST already uses a |
| // ExprWithCleanups, which will wrap this into a cir.scope anyways. |
| rval = emitSuspendExpression(cgf, *cgf.curCoro.data, e, kind, aggSlot, |
| ignoreResult, currEntryBlock, tmpResumeRValAddr, |
| /*forLValue*/ false) |
| .rv; |
| |
| if (ignoreResult || rval.isIgnored()) |
| return rval; |
| |
| if (rval.isScalar()) { |
| rval = RValue::get(cir::LoadOp::create(cgf.getBuilder(), scopeLoc, |
| rval.getValue().getType(), |
| tmpResumeRValAddr)); |
| } else if (rval.isAggregate()) { |
| // This is probably already handled via AggSlot, remove this assertion |
| // once we have a testcase and prove all pieces work. |
| cgf.cgm.errorNYI("emitSuspendExpr Aggregate"); |
| } else { // complex |
| cgf.cgm.errorNYI("emitSuspendExpr Complex"); |
| } |
| return rval; |
| } |
| |
| RValue CIRGenFunction::emitCoawaitExpr(const CoawaitExpr &e, |
| AggValueSlot aggSlot, |
| bool ignoreResult) { |
| return emitSuspendExpr(*this, e, curCoro.data->currentAwaitKind, aggSlot, |
| ignoreResult); |
| } |
| |
| mlir::LogicalResult CIRGenFunction::emitCoreturnStmt(CoreturnStmt const &s) { |
| ++curCoro.data->coreturnCount; |
| curLexScope->setCoreturn(); |
| |
| const Expr *rv = s.getOperand(); |
| if (rv && rv->getType()->isVoidType() && !isa<InitListExpr>(rv)) { |
| // Make sure to evaluate the non initlist expression of a co_return |
| // with a void expression for side effects. |
| RunCleanupsScope cleanupScope(*this); |
| emitIgnoredExpr(rv); |
| } |
| |
| if (emitStmt(s.getPromiseCall(), /*useCurrentScope=*/true).failed()) |
| return mlir::failure(); |
| // Create a new return block (if not existent) and add a branch to |
| // it. The actual return instruction is only inserted during current |
| // scope cleanup handling. |
| mlir::Location loc = getLoc(s.getSourceRange()); |
| mlir::Block *retBlock = curLexScope->getOrCreateRetBlock(*this, loc); |
| curCoro.data->finalSuspendInsPoint = |
| cir::BrOp::create(builder, loc, retBlock); |
| |
| // Insert the new block to continue codegen after branch to ret block, |
| // this will likely be an empty block. |
| builder.createBlock(builder.getBlock()->getParent()); |
| |
| return mlir::success(); |
| } |