aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/clang/lib/Sema/SemaCoroutine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/llvm-project/clang/lib/Sema/SemaCoroutine.cpp')
-rw-r--r--contrib/llvm-project/clang/lib/Sema/SemaCoroutine.cpp958
1 files changed, 657 insertions, 301 deletions
diff --git a/contrib/llvm-project/clang/lib/Sema/SemaCoroutine.cpp b/contrib/llvm-project/clang/lib/Sema/SemaCoroutine.cpp
index 3d1899a57c72..4e600fd29ee7 100644
--- a/contrib/llvm-project/clang/lib/Sema/SemaCoroutine.cpp
+++ b/contrib/llvm-project/clang/lib/Sema/SemaCoroutine.cpp
@@ -16,10 +16,12 @@
#include "CoroutineStmtBuilder.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/StmtCXX.h"
#include "clang/Basic/Builtins.h"
#include "clang/Lex/Preprocessor.h"
+#include "clang/Sema/EnterExpressionEvaluationContext.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/ScopeInfo.h"
@@ -53,18 +55,11 @@ static QualType lookupPromiseType(Sema &S, const FunctionDecl *FD,
SourceLocation KwLoc) {
const FunctionProtoType *FnType = FD->getType()->castAs<FunctionProtoType>();
const SourceLocation FuncLoc = FD->getLocation();
- // FIXME: Cache std::coroutine_traits once we've found it.
- NamespaceDecl *StdExp = S.lookupStdExperimentalNamespace();
- if (!StdExp) {
- S.Diag(KwLoc, diag::err_implied_coroutine_type_not_found)
- << "std::experimental::coroutine_traits";
- return QualType();
- }
- ClassTemplateDecl *CoroTraits = S.lookupCoroutineTraits(KwLoc, FuncLoc);
- if (!CoroTraits) {
+ ClassTemplateDecl *CoroTraits =
+ S.lookupCoroutineTraits(KwLoc, FuncLoc);
+ if (!CoroTraits)
return QualType();
- }
// Form template argument list for coroutine_traits<R, P1, P2, ...> according
// to [dcl.fct.def.coroutine]3
@@ -77,7 +72,7 @@ static QualType lookupPromiseType(Sema &S, const FunctionDecl *FD,
// If the function is a non-static member function, add the type
// of the implicit object parameter before the formal parameters.
if (auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
- if (MD->isInstance()) {
+ if (MD->isImplicitObjectMemberFunction()) {
// [over.match.funcs]4
// For non-static member functions, the type of the implicit object
// parameter is
@@ -85,7 +80,7 @@ static QualType lookupPromiseType(Sema &S, const FunctionDecl *FD,
// ref-qualifier or with the & ref-qualifier
// -- "rvalue reference to cv X" for functions declared with the &&
// ref-qualifier
- QualType T = MD->getThisType()->castAs<PointerType>()->getPointeeType();
+ QualType T = MD->getFunctionObjectParameterType();
T = FnType->getRefQualifier() == RQ_RValue
? S.Context.getRValueReferenceType(T)
: S.Context.getLValueReferenceType(T, /*SpelledAsLValue*/ true);
@@ -122,10 +117,11 @@ static QualType lookupPromiseType(Sema &S, const FunctionDecl *FD,
QualType PromiseType = S.Context.getTypeDeclType(Promise);
auto buildElaboratedType = [&]() {
- auto *NNS = NestedNameSpecifier::Create(S.Context, nullptr, StdExp);
+ auto *NNS = NestedNameSpecifier::Create(S.Context, nullptr, S.getStdNamespace());
NNS = NestedNameSpecifier::Create(S.Context, NNS, false,
CoroTrait.getTypePtr());
- return S.Context.getElaboratedType(ETK_None, NNS, PromiseType);
+ return S.Context.getElaboratedType(ElaboratedTypeKeyword::None, NNS,
+ PromiseType);
};
if (!PromiseType->getAsCXXRecordDecl()) {
@@ -141,20 +137,20 @@ static QualType lookupPromiseType(Sema &S, const FunctionDecl *FD,
return PromiseType;
}
-/// Look up the std::experimental::coroutine_handle<PromiseType>.
+/// Look up the std::coroutine_handle<PromiseType>.
static QualType lookupCoroutineHandleType(Sema &S, QualType PromiseType,
SourceLocation Loc) {
if (PromiseType.isNull())
return QualType();
- NamespaceDecl *StdExp = S.lookupStdExperimentalNamespace();
- assert(StdExp && "Should already be diagnosed");
+ NamespaceDecl *CoroNamespace = S.getStdNamespace();
+ assert(CoroNamespace && "Should already be diagnosed");
LookupResult Result(S, &S.PP.getIdentifierTable().get("coroutine_handle"),
Loc, Sema::LookupOrdinaryName);
- if (!S.LookupQualifiedName(Result, StdExp)) {
+ if (!S.LookupQualifiedName(Result, CoroNamespace)) {
S.Diag(Loc, diag::err_implied_coroutine_type_not_found)
- << "std::experimental::coroutine_handle";
+ << "std::coroutine_handle";
return QualType();
}
@@ -242,53 +238,31 @@ static bool isValidCoroutineContext(Sema &S, SourceLocation Loc,
// placeholder type shall not be a coroutine."
if (FD->getReturnType()->isUndeducedType())
DiagInvalid(DiagAutoRet);
- // [dcl.fct.def.coroutine]p1: "The parameter-declaration-clause of the
- // coroutine shall not terminate with an ellipsis that is not part of a
- // parameter-declaration."
+ // [dcl.fct.def.coroutine]p1
+ // The parameter-declaration-clause of the coroutine shall not terminate with
+ // an ellipsis that is not part of a parameter-declaration.
if (FD->isVariadic())
DiagInvalid(DiagVarargs);
return !Diagnosed;
}
-static ExprResult buildOperatorCoawaitLookupExpr(Sema &SemaRef, Scope *S,
- SourceLocation Loc) {
- DeclarationName OpName =
- SemaRef.Context.DeclarationNames.getCXXOperatorName(OO_Coawait);
- LookupResult Operators(SemaRef, OpName, SourceLocation(),
- Sema::LookupOperatorName);
- SemaRef.LookupName(Operators, S);
-
- assert(!Operators.isAmbiguous() && "Operator lookup cannot be ambiguous");
- const auto &Functions = Operators.asUnresolvedSet();
- bool IsOverloaded =
- Functions.size() > 1 ||
- (Functions.size() == 1 && isa<FunctionTemplateDecl>(*Functions.begin()));
- Expr *CoawaitOp = UnresolvedLookupExpr::Create(
- SemaRef.Context, /*NamingClass*/ nullptr, NestedNameSpecifierLoc(),
- DeclarationNameInfo(OpName, Loc), /*RequiresADL*/ true, IsOverloaded,
- Functions.begin(), Functions.end());
- assert(CoawaitOp);
- return CoawaitOp;
-}
-
/// Build a call to 'operator co_await' if there is a suitable operator for
/// the given expression.
-static ExprResult buildOperatorCoawaitCall(Sema &SemaRef, SourceLocation Loc,
- Expr *E,
- UnresolvedLookupExpr *Lookup) {
+ExprResult Sema::BuildOperatorCoawaitCall(SourceLocation Loc, Expr *E,
+ UnresolvedLookupExpr *Lookup) {
UnresolvedSet<16> Functions;
Functions.append(Lookup->decls_begin(), Lookup->decls_end());
- return SemaRef.CreateOverloadedUnaryOp(Loc, UO_Coawait, Functions, E);
+ return CreateOverloadedUnaryOp(Loc, UO_Coawait, Functions, E);
}
static ExprResult buildOperatorCoawaitCall(Sema &SemaRef, Scope *S,
SourceLocation Loc, Expr *E) {
- ExprResult R = buildOperatorCoawaitLookupExpr(SemaRef, S, Loc);
+ ExprResult R = SemaRef.BuildOperatorCoawaitLookupExpr(S, Loc);
if (R.isInvalid())
return ExprError();
- return buildOperatorCoawaitCall(SemaRef, Loc, E,
- cast<UnresolvedLookupExpr>(R.get()));
+ return SemaRef.BuildOperatorCoawaitCall(Loc, E,
+ cast<UnresolvedLookupExpr>(R.get()));
}
static ExprResult buildCoroutineHandle(Sema &S, QualType PromiseType,
@@ -347,11 +321,12 @@ static ExprResult buildMemberCall(Sema &S, Expr *Base, SourceLocation Loc,
return ExprError();
}
- return S.BuildCallExpr(nullptr, Result.get(), Loc, Args, Loc, nullptr);
+ auto EndLoc = Args.empty() ? Loc : Args.back()->getEndLoc();
+ return S.BuildCallExpr(nullptr, Result.get(), Loc, Args, EndLoc, nullptr);
}
// See if return type is coroutine-handle and if so, invoke builtin coro-resume
-// on its address. This is to enable experimental support for coroutine-handle
+// on its address. This is to enable the support for coroutine-handle
// returning await_suspend that results in a guaranteed tail call to the target
// coroutine.
static Expr *maybeTailCall(Sema &S, QualType RetType, Expr *E,
@@ -366,12 +341,34 @@ static Expr *maybeTailCall(Sema &S, QualType RetType, Expr *E,
// EvaluateBinaryTypeTrait(BTT_IsConvertible, ...) which is at the moment
// a private function in SemaExprCXX.cpp
- ExprResult AddressExpr = buildMemberCall(S, E, Loc, "address", None);
+ ExprResult AddressExpr = buildMemberCall(S, E, Loc, "address", std::nullopt);
if (AddressExpr.isInvalid())
return nullptr;
Expr *JustAddress = AddressExpr.get();
+ // FIXME: Without optimizations, the temporary result from `await_suspend()`
+ // may be put on the coroutine frame since the coroutine frame constructor
+ // will think the temporary variable will escape from the
+ // `coroutine_handle<>::address()` call. This is problematic since the
+ // coroutine should be considered to be suspended after it enters
+ // `await_suspend` so it shouldn't access/update the coroutine frame after
+ // that.
+ //
+ // See https://github.com/llvm/llvm-project/issues/65054 for the report.
+ //
+ // The long term solution may wrap the whole logic about `await-suspend`
+ // into a standalone function. This is similar to the proposed solution
+ // in tryMarkAwaitSuspendNoInline. See the comments there for details.
+ //
+ // The short term solution here is to mark `coroutine_handle<>::address()`
+ // function as always-inline so that the coroutine frame constructor won't
+ // think the temporary result is escaped incorrectly.
+ if (auto *FD = cast<CallExpr>(JustAddress)->getDirectCallee())
+ if (!FD->hasAttr<AlwaysInlineAttr>() && !FD->hasAttr<NoInlineAttr>())
+ FD->addAttr(AlwaysInlineAttr::CreateImplicit(S.getASTContext(),
+ FD->getLocation()));
+
// Check that the type of AddressExpr is void*
if (!JustAddress->getType().getTypePtr()->isVoidPointerType())
S.Diag(cast<CallExpr>(JustAddress)->getCalleeDecl()->getLocation(),
@@ -388,6 +385,63 @@ static Expr *maybeTailCall(Sema &S, QualType RetType, Expr *E,
JustAddress);
}
+/// The await_suspend call performed by co_await is essentially asynchronous
+/// to the execution of the coroutine. Inlining it normally into an unsplit
+/// coroutine can cause miscompilation because the coroutine CFG misrepresents
+/// the true control flow of the program: things that happen in the
+/// await_suspend are not guaranteed to happen prior to the resumption of the
+/// coroutine, and things that happen after the resumption of the coroutine
+/// (including its exit and the potential deallocation of the coroutine frame)
+/// are not guaranteed to happen only after the end of await_suspend.
+///
+/// See https://github.com/llvm/llvm-project/issues/56301 and
+/// https://reviews.llvm.org/D157070 for the example and the full discussion.
+///
+/// The short-term solution to this problem is to mark the call as uninlinable.
+/// But we don't want to do this if the call is known to be trivial, which is
+/// very common.
+///
+/// The long-term solution may introduce patterns like:
+///
+/// call @llvm.coro.await_suspend(ptr %awaiter, ptr %handle,
+/// ptr @awaitSuspendFn)
+///
+/// Then it is much easier to perform the safety analysis in the middle end.
+/// If it is safe to inline the call to awaitSuspend, we can replace it in the
+/// CoroEarly pass. Otherwise we could replace it in the CoroSplit pass.
+static void tryMarkAwaitSuspendNoInline(Sema &S, OpaqueValueExpr *Awaiter,
+ CallExpr *AwaitSuspend) {
+ // The method here to extract the awaiter decl is not precise.
+ // This is intentional. Since it is hard to perform the analysis in the
+ // frontend due to the complexity of C++'s type systems.
+ // And we prefer to perform such analysis in the middle end since it is
+ // easier to implement and more powerful.
+ CXXRecordDecl *AwaiterDecl =
+ Awaiter->getType().getNonReferenceType()->getAsCXXRecordDecl();
+
+ if (AwaiterDecl && AwaiterDecl->field_empty())
+ return;
+
+ FunctionDecl *FD = AwaitSuspend->getDirectCallee();
+
+ assert(FD);
+
+ // If the `await_suspend()` function is marked as `always_inline` explicitly,
+ // we should give the user the right to control the codegen.
+ if (FD->hasAttr<NoInlineAttr>() || FD->hasAttr<AlwaysInlineAttr>())
+ return;
+
+ // This is problematic if the user calls the await_suspend standalone. But on
+ // the on hand, it is not incorrect semantically since inlining is not part
+ // of the standard. On the other hand, it is relatively rare to call
+ // the await_suspend function standalone.
+ //
+ // And given we've already had the long-term plan, the current workaround
+ // looks relatively tolerant.
+ FD->addAttr(
+ NoInlineAttr::CreateImplicit(S.getASTContext(), FD->getLocation()));
+}
+
/// Build calls to await_ready, await_suspend, and await_resume for a co_await
/// expression.
/// The generated AST tries to clean up temporary objects as early as
@@ -422,8 +476,8 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise,
return Result.get();
};
- CallExpr *AwaitReady =
- cast_or_null<CallExpr>(BuildSubExpr(ACT::ACT_Ready, "await_ready", None));
+ CallExpr *AwaitReady = cast_or_null<CallExpr>(
+ BuildSubExpr(ACT::ACT_Ready, "await_ready", std::nullopt));
if (!AwaitReady)
return Calls;
if (!AwaitReady->getType()->isDependentType()) {
@@ -459,7 +513,11 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise,
// type Z.
QualType RetType = AwaitSuspend->getCallReturnType(S.Context);
- // Experimental support for coroutine_handle returning await_suspend.
+ // We need to mark await_suspend as noinline temporarily. See the comment
+ // of tryMarkAwaitSuspendNoInline for details.
+ tryMarkAwaitSuspendNoInline(S, Operand, AwaitSuspend);
+
+ // Support for coroutine_handle returning await_suspend.
if (Expr *TailCallSuspend =
maybeTailCall(S, RetType, AwaitSuspend, Loc))
// Note that we don't wrap the expression with ExprWithCleanups here
@@ -484,7 +542,7 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, VarDecl *CoroPromise,
}
}
- BuildSubExpr(ACT::ACT_Resume, "await_resume", None);
+ BuildSubExpr(ACT::ACT_Resume, "await_resume", std::nullopt);
// Make sure the awaiter object gets a chance to be cleaned up.
S.Cleanup.setExprNeedsCleanups(true);
@@ -509,10 +567,10 @@ VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) {
assert(isa<FunctionDecl>(CurContext) && "not in a function scope");
auto *FD = cast<FunctionDecl>(CurContext);
bool IsThisDependentType = [&] {
- if (auto *MD = dyn_cast_or_null<CXXMethodDecl>(FD))
- return MD->isInstance() && MD->getThisType()->isDependentType();
- else
- return false;
+ if (const auto *MD = dyn_cast_if_present<CXXMethodDecl>(FD))
+ return MD->isImplicitObjectMemberFunction() &&
+ MD->getThisType()->isDependentType();
+ return false;
}();
QualType T = FD->getType()->isDependentType() || IsThisDependentType
@@ -537,7 +595,7 @@ VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) {
// Add implicit object parameter.
if (auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
- if (MD->isInstance() && !isLambdaCallOperator(MD)) {
+ if (MD->isImplicitObjectMemberFunction() && !isLambdaCallOperator(MD)) {
ExprResult ThisExpr = ActOnCXXThis(Loc);
if (ThisExpr.isInvalid())
return nullptr;
@@ -584,8 +642,12 @@ VarDecl *Sema::buildCoroutinePromise(SourceLocation Loc) {
/*TopLevelOfInitList=*/false,
/*TreatUnavailableAsInvalid=*/false);
- // Attempt to initialize the promise type with the arguments.
- // If that fails, fall back to the promise type's default constructor.
+ // [dcl.fct.def.coroutine]5.7
+ // promise-constructor-arguments is determined as follows: overload
+ // resolution is performed on a promise constructor call created by
+ // assembling an argument list q_1 ... q_n . If a viable constructor is
+ // found ([over.match.viable]), then promise-constructor-arguments is ( q_1
+ // , ..., q_n ), otherwise promise-constructor-arguments is empty.
if (InitSeq) {
ExprResult Result = InitSeq.Perform(*this, Entity, Kind, CtorArgExprs);
if (Result.isInvalid()) {
@@ -653,6 +715,10 @@ static void checkNoThrow(Sema &S, const Stmt *E,
return;
}
if (ThrowingDecls.empty()) {
+ // [dcl.fct.def.coroutine]p15
+ // The expression co_await promise.final_suspend() shall not be
+ // potentially-throwing ([except.spec]).
+ //
// First time seeing an error, emit the error message.
S.Diag(cast<FunctionDecl>(S.CurContext)->getLocation(),
diag::err_coroutine_promise_final_suspend_requires_nothrow);
@@ -660,32 +726,32 @@ static void checkNoThrow(Sema &S, const Stmt *E,
ThrowingDecls.insert(D);
}
};
- auto SC = E->getStmtClass();
- if (SC == Expr::CXXConstructExprClass) {
- auto const *Ctor = cast<CXXConstructExpr>(E)->getConstructor();
+
+ if (auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+ CXXConstructorDecl *Ctor = CE->getConstructor();
checkDeclNoexcept(Ctor);
// Check the corresponding destructor of the constructor.
- checkDeclNoexcept(Ctor->getParent()->getDestructor(), true);
- } else if (SC == Expr::CallExprClass || SC == Expr::CXXMemberCallExprClass ||
- SC == Expr::CXXOperatorCallExprClass) {
- if (!cast<CallExpr>(E)->isTypeDependent()) {
- checkDeclNoexcept(cast<CallExpr>(E)->getCalleeDecl());
- auto ReturnType = cast<CallExpr>(E)->getCallReturnType(S.getASTContext());
- // Check the destructor of the call return type, if any.
- if (ReturnType.isDestructedType() ==
- QualType::DestructionKind::DK_cxx_destructor) {
- const auto *T =
- cast<RecordType>(ReturnType.getCanonicalType().getTypePtr());
- checkDeclNoexcept(
- dyn_cast<CXXRecordDecl>(T->getDecl())->getDestructor(), true);
- }
+ checkDeclNoexcept(Ctor->getParent()->getDestructor(), /*IsDtor=*/true);
+ } else if (auto *CE = dyn_cast<CallExpr>(E)) {
+ if (CE->isTypeDependent())
+ return;
+
+ checkDeclNoexcept(CE->getCalleeDecl());
+ QualType ReturnType = CE->getCallReturnType(S.getASTContext());
+ // Check the destructor of the call return type, if any.
+ if (ReturnType.isDestructedType() ==
+ QualType::DestructionKind::DK_cxx_destructor) {
+ const auto *T =
+ cast<RecordType>(ReturnType.getCanonicalType().getTypePtr());
+ checkDeclNoexcept(cast<CXXRecordDecl>(T->getDecl())->getDestructor(),
+ /*IsDtor=*/true);
+ }
+ } else
+ for (const auto *Child : E->children()) {
+ if (!Child)
+ continue;
+ checkNoThrow(S, Child, ThrowingDecls);
}
- }
- for (const auto *Child : E->children()) {
- if (!Child)
- continue;
- checkNoThrow(S, Child, ThrowingDecls);
- }
}
bool Sema::checkFinalSuspendNoThrow(const Stmt *FinalSuspend) {
@@ -708,6 +774,9 @@ bool Sema::checkFinalSuspendNoThrow(const Stmt *FinalSuspend) {
bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc,
StringRef Keyword) {
+ // Ignore previous expr evaluation contexts.
+ EnterExpressionEvaluationContext PotentiallyEvaluated(
+ *this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated);
if (!checkCoroutineContext(*this, KWLoc, Keyword))
return false;
auto *ScopeInfo = getCurFunction();
@@ -724,14 +793,15 @@ bool Sema::ActOnCoroutineBodyStart(Scope *SC, SourceLocation KWLoc,
SourceLocation Loc = Fn->getLocation();
// Build the initial suspend point
auto buildSuspends = [&](StringRef Name) mutable -> StmtResult {
- ExprResult Suspend =
- buildPromiseCall(*this, ScopeInfo->CoroutinePromise, Loc, Name, None);
- if (Suspend.isInvalid())
+ ExprResult Operand = buildPromiseCall(*this, ScopeInfo->CoroutinePromise,
+ Loc, Name, std::nullopt);
+ if (Operand.isInvalid())
return StmtError();
- Suspend = buildOperatorCoawaitCall(*this, SC, Loc, Suspend.get());
+ ExprResult Suspend =
+ buildOperatorCoawaitCall(*this, SC, Loc, Operand.get());
if (Suspend.isInvalid())
return StmtError();
- Suspend = BuildResolvedCoawaitExpr(Loc, Suspend.get(),
+ Suspend = BuildResolvedCoawaitExpr(Loc, Operand.get(), Suspend.get(),
/*IsImplicit*/ true);
Suspend = ActOnFinishFullExpr(Suspend.get(), /*DiscardedValue*/ false);
if (Suspend.isInvalid()) {
@@ -773,8 +843,8 @@ static bool isWithinCatchScope(Scope *S) {
// }();
// }
// }
- while (S && !(S->getFlags() & Scope::FnScope)) {
- if (S->getFlags() & Scope::CatchScope)
+ while (S && !S->isFunctionScope()) {
+ if (S->isCatchScope())
return true;
S = S->getParent();
}
@@ -786,126 +856,158 @@ static bool isWithinCatchScope(Scope *S) {
// function-body *outside of a handler* [...] A context within a function
// where an await-expression can appear is called a suspension context of the
// function."
-static void checkSuspensionContext(Sema &S, SourceLocation Loc,
+static bool checkSuspensionContext(Sema &S, SourceLocation Loc,
StringRef Keyword) {
// First emphasis of [expr.await]p2: must be a potentially evaluated context.
// That is, 'co_await' and 'co_yield' cannot appear in subexpressions of
// \c sizeof.
- if (S.isUnevaluatedContext())
+ if (S.isUnevaluatedContext()) {
S.Diag(Loc, diag::err_coroutine_unevaluated_context) << Keyword;
+ return false;
+ }
// Second emphasis of [expr.await]p2: must be outside of an exception handler.
- if (isWithinCatchScope(S.getCurScope()))
+ if (isWithinCatchScope(S.getCurScope())) {
S.Diag(Loc, diag::err_coroutine_within_handler) << Keyword;
+ return false;
+ }
+
+ return true;
}
ExprResult Sema::ActOnCoawaitExpr(Scope *S, SourceLocation Loc, Expr *E) {
+ if (!checkSuspensionContext(*this, Loc, "co_await"))
+ return ExprError();
+
if (!ActOnCoroutineBodyStart(S, Loc, "co_await")) {
CorrectDelayedTyposInExpr(E);
return ExprError();
}
- checkSuspensionContext(*this, Loc, "co_await");
-
- if (E->getType()->isPlaceholderType()) {
+ if (E->hasPlaceholderType()) {
ExprResult R = CheckPlaceholderExpr(E);
if (R.isInvalid()) return ExprError();
E = R.get();
}
- ExprResult Lookup = buildOperatorCoawaitLookupExpr(*this, S, Loc);
+ ExprResult Lookup = BuildOperatorCoawaitLookupExpr(S, Loc);
if (Lookup.isInvalid())
return ExprError();
return BuildUnresolvedCoawaitExpr(Loc, E,
cast<UnresolvedLookupExpr>(Lookup.get()));
}
-ExprResult Sema::BuildUnresolvedCoawaitExpr(SourceLocation Loc, Expr *E,
+ExprResult Sema::BuildOperatorCoawaitLookupExpr(Scope *S, SourceLocation Loc) {
+ DeclarationName OpName =
+ Context.DeclarationNames.getCXXOperatorName(OO_Coawait);
+ LookupResult Operators(*this, OpName, SourceLocation(),
+ Sema::LookupOperatorName);
+ LookupName(Operators, S);
+
+ assert(!Operators.isAmbiguous() && "Operator lookup cannot be ambiguous");
+ const auto &Functions = Operators.asUnresolvedSet();
+ bool IsOverloaded =
+ Functions.size() > 1 ||
+ (Functions.size() == 1 && isa<FunctionTemplateDecl>(*Functions.begin()));
+ Expr *CoawaitOp = UnresolvedLookupExpr::Create(
+ Context, /*NamingClass*/ nullptr, NestedNameSpecifierLoc(),
+ DeclarationNameInfo(OpName, Loc), /*RequiresADL*/ true, IsOverloaded,
+ Functions.begin(), Functions.end());
+ assert(CoawaitOp);
+ return CoawaitOp;
+}
+
+// Attempts to resolve and build a CoawaitExpr from "raw" inputs, bailing out to
+// DependentCoawaitExpr if needed.
+ExprResult Sema::BuildUnresolvedCoawaitExpr(SourceLocation Loc, Expr *Operand,
UnresolvedLookupExpr *Lookup) {
auto *FSI = checkCoroutineContext(*this, Loc, "co_await");
if (!FSI)
return ExprError();
- if (E->getType()->isPlaceholderType()) {
- ExprResult R = CheckPlaceholderExpr(E);
+ if (Operand->hasPlaceholderType()) {
+ ExprResult R = CheckPlaceholderExpr(Operand);
if (R.isInvalid())
return ExprError();
- E = R.get();
+ Operand = R.get();
}
auto *Promise = FSI->CoroutinePromise;
if (Promise->getType()->isDependentType()) {
- Expr *Res =
- new (Context) DependentCoawaitExpr(Loc, Context.DependentTy, E, Lookup);
+ Expr *Res = new (Context)
+ DependentCoawaitExpr(Loc, Context.DependentTy, Operand, Lookup);
return Res;
}
auto *RD = Promise->getType()->getAsCXXRecordDecl();
+ auto *Transformed = Operand;
if (lookupMember(*this, "await_transform", RD, Loc)) {
- ExprResult R = buildPromiseCall(*this, Promise, Loc, "await_transform", E);
+ ExprResult R =
+ buildPromiseCall(*this, Promise, Loc, "await_transform", Operand);
if (R.isInvalid()) {
Diag(Loc,
diag::note_coroutine_promise_implicit_await_transform_required_here)
- << E->getSourceRange();
+ << Operand->getSourceRange();
return ExprError();
}
- E = R.get();
+ Transformed = R.get();
}
- ExprResult Awaitable = buildOperatorCoawaitCall(*this, Loc, E, Lookup);
- if (Awaitable.isInvalid())
+ ExprResult Awaiter = BuildOperatorCoawaitCall(Loc, Transformed, Lookup);
+ if (Awaiter.isInvalid())
return ExprError();
- return BuildResolvedCoawaitExpr(Loc, Awaitable.get());
+ return BuildResolvedCoawaitExpr(Loc, Operand, Awaiter.get());
}
-ExprResult Sema::BuildResolvedCoawaitExpr(SourceLocation Loc, Expr *E,
- bool IsImplicit) {
+ExprResult Sema::BuildResolvedCoawaitExpr(SourceLocation Loc, Expr *Operand,
+ Expr *Awaiter, bool IsImplicit) {
auto *Coroutine = checkCoroutineContext(*this, Loc, "co_await", IsImplicit);
if (!Coroutine)
return ExprError();
- if (E->getType()->isPlaceholderType()) {
- ExprResult R = CheckPlaceholderExpr(E);
+ if (Awaiter->hasPlaceholderType()) {
+ ExprResult R = CheckPlaceholderExpr(Awaiter);
if (R.isInvalid()) return ExprError();
- E = R.get();
+ Awaiter = R.get();
}
- if (E->getType()->isDependentType()) {
+ if (Awaiter->getType()->isDependentType()) {
Expr *Res = new (Context)
- CoawaitExpr(Loc, Context.DependentTy, E, IsImplicit);
+ CoawaitExpr(Loc, Context.DependentTy, Operand, Awaiter, IsImplicit);
return Res;
}
// If the expression is a temporary, materialize it as an lvalue so that we
// can use it multiple times.
- if (E->isPRValue())
- E = CreateMaterializeTemporaryExpr(E->getType(), E, true);
+ if (Awaiter->isPRValue())
+ Awaiter = CreateMaterializeTemporaryExpr(Awaiter->getType(), Awaiter, true);
// The location of the `co_await` token cannot be used when constructing
// the member call expressions since it's before the location of `Expr`, which
// is used as the start of the member call expression.
- SourceLocation CallLoc = E->getExprLoc();
+ SourceLocation CallLoc = Awaiter->getExprLoc();
// Build the await_ready, await_suspend, await_resume calls.
- ReadySuspendResumeResult RSS = buildCoawaitCalls(
- *this, Coroutine->CoroutinePromise, CallLoc, E);
+ ReadySuspendResumeResult RSS =
+ buildCoawaitCalls(*this, Coroutine->CoroutinePromise, CallLoc, Awaiter);
if (RSS.IsInvalid)
return ExprError();
- Expr *Res =
- new (Context) CoawaitExpr(Loc, E, RSS.Results[0], RSS.Results[1],
- RSS.Results[2], RSS.OpaqueValue, IsImplicit);
+ Expr *Res = new (Context)
+ CoawaitExpr(Loc, Operand, Awaiter, RSS.Results[0], RSS.Results[1],
+ RSS.Results[2], RSS.OpaqueValue, IsImplicit);
return Res;
}
ExprResult Sema::ActOnCoyieldExpr(Scope *S, SourceLocation Loc, Expr *E) {
+ if (!checkSuspensionContext(*this, Loc, "co_yield"))
+ return ExprError();
+
if (!ActOnCoroutineBodyStart(S, Loc, "co_yield")) {
CorrectDelayedTyposInExpr(E);
return ExprError();
}
- checkSuspensionContext(*this, Loc, "co_yield");
-
// Build yield_value call.
ExprResult Awaitable = buildPromiseCall(
*this, getCurFunction()->CoroutinePromise, Loc, "yield_value", E);
@@ -924,14 +1026,16 @@ ExprResult Sema::BuildCoyieldExpr(SourceLocation Loc, Expr *E) {
if (!Coroutine)
return ExprError();
- if (E->getType()->isPlaceholderType()) {
+ if (E->hasPlaceholderType()) {
ExprResult R = CheckPlaceholderExpr(E);
if (R.isInvalid()) return ExprError();
E = R.get();
}
+ Expr *Operand = E;
+
if (E->getType()->isDependentType()) {
- Expr *Res = new (Context) CoyieldExpr(Loc, Context.DependentTy, E);
+ Expr *Res = new (Context) CoyieldExpr(Loc, Context.DependentTy, Operand, E);
return Res;
}
@@ -947,7 +1051,7 @@ ExprResult Sema::BuildCoyieldExpr(SourceLocation Loc, Expr *E) {
return ExprError();
Expr *Res =
- new (Context) CoyieldExpr(Loc, E, RSS.Results[0], RSS.Results[1],
+ new (Context) CoyieldExpr(Loc, Operand, E, RSS.Results[0], RSS.Results[1],
RSS.Results[2], RSS.OpaqueValue);
return Res;
@@ -967,8 +1071,8 @@ StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc, Expr *E,
if (!FSI)
return StmtError();
- if (E && E->getType()->isPlaceholderType() &&
- !E->getType()->isSpecificPlaceholderType(BuiltinType::Overload)) {
+ if (E && E->hasPlaceholderType() &&
+ !E->hasPlaceholderType(BuiltinType::Overload)) {
ExprResult R = CheckPlaceholderExpr(E);
if (R.isInvalid()) return StmtError();
E = R.get();
@@ -981,7 +1085,7 @@ StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc, Expr *E,
PC = buildPromiseCall(*this, Promise, Loc, "return_value", E);
} else {
E = MakeFullDiscardedValueExpr(E).get();
- PC = buildPromiseCall(*this, Promise, Loc, "return_void", None);
+ PC = buildPromiseCall(*this, Promise, Loc, "return_void", std::nullopt);
}
if (PC.isInvalid())
return StmtError();
@@ -1000,9 +1104,8 @@ static Expr *buildStdNoThrowDeclRef(Sema &S, SourceLocation Loc) {
LookupResult Result(S, &S.PP.getIdentifierTable().get("nothrow"), Loc,
Sema::LookupOrdinaryName);
if (!S.LookupQualifiedName(Result, Std)) {
- // FIXME: <experimental/coroutine> should have been included already.
- // If we require it to include <new> then this diagnostic is no longer
- // needed.
+ // <coroutine> is not requred to include <new>, so we couldn't omit
+ // the check here.
S.Diag(Loc, diag::err_implicit_coroutine_std_nothrow_type_not_found);
return nullptr;
}
@@ -1023,29 +1126,55 @@ static Expr *buildStdNoThrowDeclRef(Sema &S, SourceLocation Loc) {
return DR.get();
}
-// Find an appropriate delete for the promise.
-static FunctionDecl *findDeleteForPromise(Sema &S, SourceLocation Loc,
- QualType PromiseType) {
- FunctionDecl *OperatorDelete = nullptr;
+static TypeSourceInfo *getTypeSourceInfoForStdAlignValT(Sema &S,
+ SourceLocation Loc) {
+ EnumDecl *StdAlignValT = S.getStdAlignValT();
+ QualType StdAlignValDecl = S.Context.getTypeDeclType(StdAlignValT);
+ return S.Context.getTrivialTypeSourceInfo(StdAlignValDecl);
+}
+// Find an appropriate delete for the promise.
+static bool findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseType,
+ FunctionDecl *&OperatorDelete) {
DeclarationName DeleteName =
S.Context.DeclarationNames.getCXXOperatorName(OO_Delete);
auto *PointeeRD = PromiseType->getAsCXXRecordDecl();
assert(PointeeRD && "PromiseType must be a CxxRecordDecl type");
- if (S.FindDeallocationFunction(Loc, PointeeRD, DeleteName, OperatorDelete))
- return nullptr;
+ const bool Overaligned = S.getLangOpts().CoroAlignedAllocation;
+
+ // [dcl.fct.def.coroutine]p12
+ // The deallocation function's name is looked up by searching for it in the
+ // scope of the promise type. If nothing is found, a search is performed in
+ // the global scope.
+ if (S.FindDeallocationFunction(Loc, PointeeRD, DeleteName, OperatorDelete,
+ /*Diagnose*/ true, /*WantSize*/ true,
+ /*WantAligned*/ Overaligned))
+ return false;
+ // [dcl.fct.def.coroutine]p12
+ // If both a usual deallocation function with only a pointer parameter and a
+ // usual deallocation function with both a pointer parameter and a size
+ // parameter are found, then the selected deallocation function shall be the
+ // one with two parameters. Otherwise, the selected deallocation function
+ // shall be the function with one parameter.
if (!OperatorDelete) {
// Look for a global declaration.
- const bool CanProvideSize = S.isCompleteType(Loc, PromiseType);
- const bool Overaligned = false;
+ // Coroutines can always provide their required size.
+ const bool CanProvideSize = true;
+ // Sema::FindUsualDeallocationFunction will try to find the one with two
+ // parameters first. It will return the deallocation function with one
+ // parameter if failed.
OperatorDelete = S.FindUsualDeallocationFunction(Loc, CanProvideSize,
Overaligned, DeleteName);
+
+ if (!OperatorDelete)
+ return false;
}
+
S.MarkFunctionReferenced(Loc, OperatorDelete);
- return OperatorDelete;
+ return true;
}
@@ -1067,8 +1196,21 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
return;
}
- // Coroutines [stmt.return]p1:
- // A return statement shall not appear in a coroutine.
+ // The always_inline attribute doesn't reliably apply to a coroutine,
+ // because the coroutine will be split into pieces and some pieces
+ // might be called indirectly, as in a virtual call. Even the ramp
+ // function cannot be inlined at -O0, due to pipeline ordering
+ // problems (see https://llvm.org/PR53413). Tell the user about it.
+ if (FD->hasAttr<AlwaysInlineAttr>())
+ Diag(FD->getLocation(), diag::warn_always_inline_coroutine);
+
+ // The design of coroutines means we cannot allow use of VLAs within one, so
+ // diagnose if we've seen a VLA in the body of this function.
+ if (Fn->FirstVLALoc.isValid())
+ Diag(Fn->FirstVLALoc, diag::err_vla_in_coroutine_unsupported);
+
+ // [stmt.return.coroutine]p1:
+ // A coroutine shall not enclose a return statement ([stmt.return]).
if (Fn->FirstReturnLoc.isValid()) {
assert(Fn->FirstCoroutineStmtLoc.isValid() &&
"first coroutine location not set");
@@ -1076,6 +1218,12 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
Diag(Fn->FirstCoroutineStmtLoc, diag::note_declared_coroutine_here)
<< Fn->getFirstCoroutineStmtKeyword();
}
+
+ // Coroutines will get splitted into pieces. The GNU address of label
+ // extension wouldn't be meaningful in coroutines.
+ for (AddrLabelExpr *ALE : Fn->AddrLabels)
+ Diag(ALE->getBeginLoc(), diag::err_coro_invalid_addr_of_label);
+
CoroutineStmtBuilder Builder(*this, *FD, *Fn, Body);
if (Builder.isInvalid() || !Builder.buildStatements())
return FD->setInvalidDecl();
@@ -1084,6 +1232,18 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) {
Body = CoroutineBodyStmt::Create(Context, Builder);
}
+static CompoundStmt *buildCoroutineBody(Stmt *Body, ASTContext &Context) {
+ if (auto *CS = dyn_cast<CompoundStmt>(Body))
+ return CS;
+
+ // The body of the coroutine may be a try statement if it is in
+ // 'function-try-block' syntax. Here we wrap it into a compound
+ // statement for consistency.
+ assert(isa<CXXTryStmt>(Body) && "Unimaged coroutine body type");
+ return CompoundStmt::Create(Context, {Body}, FPOptionsOverride(),
+ SourceLocation(), SourceLocation());
+}
+
CoroutineStmtBuilder::CoroutineStmtBuilder(Sema &S, FunctionDecl &FD,
sema::FunctionScopeInfo &Fn,
Stmt *Body)
@@ -1091,7 +1251,7 @@ CoroutineStmtBuilder::CoroutineStmtBuilder(Sema &S, FunctionDecl &FD,
IsPromiseDependentType(
!Fn.CoroutinePromise ||
Fn.CoroutinePromise->getType()->isDependentType()) {
- this->Body = Body;
+ this->Body = buildCoroutineBody(Body, S.getASTContext());
for (auto KV : Fn.CoroutineParameterMoves)
this->ParamMovesVector.push_back(KV.second);
@@ -1169,12 +1329,15 @@ bool CoroutineStmtBuilder::makeReturnOnAllocFailure() {
assert(!IsPromiseDependentType &&
"cannot make statement while the promise type is dependent");
- // [dcl.fct.def.coroutine]/8
- // The unqualified-id get_return_object_on_allocation_failure is looked up in
- // the scope of class P by class member access lookup (3.4.5). ...
- // If an allocation function returns nullptr, ... the coroutine return value
- // is obtained by a call to ... get_return_object_on_allocation_failure().
-
+ // [dcl.fct.def.coroutine]p10
+ // If a search for the name get_return_object_on_allocation_failure in
+ // the scope of the promise type ([class.member.lookup]) finds any
+ // declarations, then the result of a call to an allocation function used to
+ // obtain storage for the coroutine state is assumed to return nullptr if it
+ // fails to obtain storage, ... If the allocation function returns nullptr,
+ // ... and the return value is obtained by a call to
+ // T::get_return_object_on_allocation_failure(), where T is the
+ // promise type.
DeclarationName DN =
S.PP.getIdentifierInfo("get_return_object_on_allocation_failure");
LookupResult Found(S, DN, Loc, Sema::LookupMemberName);
@@ -1209,47 +1372,13 @@ bool CoroutineStmtBuilder::makeReturnOnAllocFailure() {
return true;
}
-bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
- // Form and check allocation and deallocation calls.
- assert(!IsPromiseDependentType &&
- "cannot make statement while the promise type is dependent");
- QualType PromiseType = Fn.CoroutinePromise->getType();
-
- if (S.RequireCompleteType(Loc, PromiseType, diag::err_incomplete_type))
- return false;
-
- const bool RequiresNoThrowAlloc = ReturnStmtOnAllocFailure != nullptr;
-
- // [dcl.fct.def.coroutine]/7
- // Lookup allocation functions using a parameter list composed of the
- // requested size of the coroutine state being allocated, followed by
- // the coroutine function's arguments. If a matching allocation function
- // exists, use it. Otherwise, use an allocation function that just takes
- // the requested size.
-
- FunctionDecl *OperatorNew = nullptr;
- FunctionDecl *OperatorDelete = nullptr;
- FunctionDecl *UnusedResult = nullptr;
- bool PassAlignment = false;
- SmallVector<Expr *, 1> PlacementArgs;
-
- // [dcl.fct.def.coroutine]/7
- // "The allocation function’s name is looked up in the scope of P.
- // [...] If the lookup finds an allocation function in the scope of P,
- // overload resolution is performed on a function call created by assembling
- // an argument list. The first argument is the amount of space requested,
- // and has type std::size_t. The lvalues p1 ... pn are the succeeding
- // arguments."
- //
- // ...where "p1 ... pn" are defined earlier as:
- //
- // [dcl.fct.def.coroutine]/3
- // "For a coroutine f that is a non-static member function, let P1 denote the
- // type of the implicit object parameter (13.3.1) and P2 ... Pn be the types
- // of the function parameters; otherwise let P1 ... Pn be the types of the
- // function parameters. Let p1 ... pn be lvalues denoting those objects."
+// Collect placement arguments for allocation function of coroutine FD.
+// Return true if we collect placement arguments succesfully. Return false,
+// otherwise.
+static bool collectPlacementArgs(Sema &S, FunctionDecl &FD, SourceLocation Loc,
+ SmallVectorImpl<Expr *> &PlacementArgs) {
if (auto *MD = dyn_cast<CXXMethodDecl>(&FD)) {
- if (MD->isInstance() && !isLambdaCallOperator(MD)) {
+ if (MD->isImplicitObjectMemberFunction() && !isLambdaCallOperator(MD)) {
ExprResult ThisExpr = S.ActOnCXXThis(Loc);
if (ThisExpr.isInvalid())
return false;
@@ -1259,6 +1388,7 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
PlacementArgs.push_back(ThisExpr.get());
}
}
+
for (auto *PD : FD.parameters()) {
if (PD->getType()->isDependentType())
continue;
@@ -1273,34 +1403,154 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
PlacementArgs.push_back(PDRefExpr.get());
}
- S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Class,
- /*DeleteScope*/ Sema::AFS_Both, PromiseType,
- /*isArray*/ false, PassAlignment, PlacementArgs,
- OperatorNew, UnusedResult, /*Diagnose*/ false);
- // [dcl.fct.def.coroutine]/7
- // "If no matching function is found, overload resolution is performed again
- // on a function call created by passing just the amount of space required as
- // an argument of type std::size_t."
- if (!OperatorNew && !PlacementArgs.empty()) {
- PlacementArgs.clear();
- S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Class,
+ return true;
+}
+
+bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
+ // Form and check allocation and deallocation calls.
+ assert(!IsPromiseDependentType &&
+ "cannot make statement while the promise type is dependent");
+ QualType PromiseType = Fn.CoroutinePromise->getType();
+
+ if (S.RequireCompleteType(Loc, PromiseType, diag::err_incomplete_type))
+ return false;
+
+ const bool RequiresNoThrowAlloc = ReturnStmtOnAllocFailure != nullptr;
+
+ // According to [dcl.fct.def.coroutine]p9, Lookup allocation functions using a
+ // parameter list composed of the requested size of the coroutine state being
+ // allocated, followed by the coroutine function's arguments. If a matching
+ // allocation function exists, use it. Otherwise, use an allocation function
+ // that just takes the requested size.
+ //
+ // [dcl.fct.def.coroutine]p9
+ // An implementation may need to allocate additional storage for a
+ // coroutine.
+ // This storage is known as the coroutine state and is obtained by calling a
+ // non-array allocation function ([basic.stc.dynamic.allocation]). The
+ // allocation function's name is looked up by searching for it in the scope of
+ // the promise type.
+ // - If any declarations are found, overload resolution is performed on a
+ // function call created by assembling an argument list. The first argument is
+ // the amount of space requested, and has type std::size_t. The
+ // lvalues p1 ... pn are the succeeding arguments.
+ //
+ // ...where "p1 ... pn" are defined earlier as:
+ //
+ // [dcl.fct.def.coroutine]p3
+ // The promise type of a coroutine is `std::coroutine_traits<R, P1, ...,
+ // Pn>`
+ // , where R is the return type of the function, and `P1, ..., Pn` are the
+ // sequence of types of the non-object function parameters, preceded by the
+ // type of the object parameter ([dcl.fct]) if the coroutine is a non-static
+ // member function. [dcl.fct.def.coroutine]p4 In the following, p_i is an
+ // lvalue of type P_i, where p1 denotes the object parameter and p_i+1 denotes
+ // the i-th non-object function parameter for a non-static member function,
+ // and p_i denotes the i-th function parameter otherwise. For a non-static
+ // member function, q_1 is an lvalue that denotes *this; any other q_i is an
+ // lvalue that denotes the parameter copy corresponding to p_i.
+
+ FunctionDecl *OperatorNew = nullptr;
+ SmallVector<Expr *, 1> PlacementArgs;
+
+ const bool PromiseContainsNew = [this, &PromiseType]() -> bool {
+ DeclarationName NewName =
+ S.getASTContext().DeclarationNames.getCXXOperatorName(OO_New);
+ LookupResult R(S, NewName, Loc, Sema::LookupOrdinaryName);
+
+ if (PromiseType->isRecordType())
+ S.LookupQualifiedName(R, PromiseType->getAsCXXRecordDecl());
+
+ return !R.empty() && !R.isAmbiguous();
+ }();
+
+ // Helper function to indicate whether the last lookup found the aligned
+ // allocation function.
+ bool PassAlignment = S.getLangOpts().CoroAlignedAllocation;
+ auto LookupAllocationFunction = [&](Sema::AllocationFunctionScope NewScope =
+ Sema::AFS_Both,
+ bool WithoutPlacementArgs = false,
+ bool ForceNonAligned = false) {
+ // [dcl.fct.def.coroutine]p9
+ // The allocation function's name is looked up by searching for it in the
+ // scope of the promise type.
+ // - If any declarations are found, ...
+ // - If no declarations are found in the scope of the promise type, a search
+ // is performed in the global scope.
+ if (NewScope == Sema::AFS_Both)
+ NewScope = PromiseContainsNew ? Sema::AFS_Class : Sema::AFS_Global;
+
+ PassAlignment = !ForceNonAligned && S.getLangOpts().CoroAlignedAllocation;
+ FunctionDecl *UnusedResult = nullptr;
+ S.FindAllocationFunctions(Loc, SourceRange(), NewScope,
/*DeleteScope*/ Sema::AFS_Both, PromiseType,
- /*isArray*/ false, PassAlignment, PlacementArgs,
+ /*isArray*/ false, PassAlignment,
+ WithoutPlacementArgs ? MultiExprArg{}
+ : PlacementArgs,
OperatorNew, UnusedResult, /*Diagnose*/ false);
- }
+ };
- // [dcl.fct.def.coroutine]/7
- // "The allocation function’s name is looked up in the scope of P. If this
- // lookup fails, the allocation function’s name is looked up in the global
- // scope."
- if (!OperatorNew) {
- S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Global,
- /*DeleteScope*/ Sema::AFS_Both, PromiseType,
- /*isArray*/ false, PassAlignment, PlacementArgs,
- OperatorNew, UnusedResult);
+ // We don't expect to call to global operator new with (size, p0, …, pn).
+ // So if we choose to lookup the allocation function in global scope, we
+ // shouldn't lookup placement arguments.
+ if (PromiseContainsNew && !collectPlacementArgs(S, FD, Loc, PlacementArgs))
+ return false;
+
+ LookupAllocationFunction();
+
+ if (PromiseContainsNew && !PlacementArgs.empty()) {
+ // [dcl.fct.def.coroutine]p9
+ // If no viable function is found ([over.match.viable]), overload
+ // resolution
+ // is performed again on a function call created by passing just the amount
+ // of space required as an argument of type std::size_t.
+ //
+ // Proposed Change of [dcl.fct.def.coroutine]p9 in P2014R0:
+ // Otherwise, overload resolution is performed again on a function call
+ // created
+ // by passing the amount of space requested as an argument of type
+ // std::size_t as the first argument, and the requested alignment as
+ // an argument of type std:align_val_t as the second argument.
+ if (!OperatorNew ||
+ (S.getLangOpts().CoroAlignedAllocation && !PassAlignment))
+ LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class,
+ /*WithoutPlacementArgs*/ true);
}
+ // Proposed Change of [dcl.fct.def.coroutine]p12 in P2014R0:
+ // Otherwise, overload resolution is performed again on a function call
+ // created
+ // by passing the amount of space requested as an argument of type
+ // std::size_t as the first argument, and the lvalues p1 ... pn as the
+ // succeeding arguments. Otherwise, overload resolution is performed again
+ // on a function call created by passing just the amount of space required as
+ // an argument of type std::size_t.
+ //
+ // So within the proposed change in P2014RO, the priority order of aligned
+ // allocation functions wiht promise_type is:
+ //
+ // void* operator new( std::size_t, std::align_val_t, placement_args... );
+ // void* operator new( std::size_t, std::align_val_t);
+ // void* operator new( std::size_t, placement_args... );
+ // void* operator new( std::size_t);
+
+ // Helper variable to emit warnings.
+ bool FoundNonAlignedInPromise = false;
+ if (PromiseContainsNew && S.getLangOpts().CoroAlignedAllocation)
+ if (!OperatorNew || !PassAlignment) {
+ FoundNonAlignedInPromise = OperatorNew;
+
+ LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class,
+ /*WithoutPlacementArgs*/ false,
+ /*ForceNonAligned*/ true);
+
+ if (!OperatorNew && !PlacementArgs.empty())
+ LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class,
+ /*WithoutPlacementArgs*/ true,
+ /*ForceNonAligned*/ true);
+ }
+
bool IsGlobalOverload =
OperatorNew && !isa<CXXRecordDecl>(OperatorNew->getDeclContext());
// If we didn't find a class-local new declaration and non-throwing new
@@ -1312,14 +1562,27 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
return false;
PlacementArgs = {StdNoThrow};
OperatorNew = nullptr;
- S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Both,
- /*DeleteScope*/ Sema::AFS_Both, PromiseType,
- /*isArray*/ false, PassAlignment, PlacementArgs,
- OperatorNew, UnusedResult);
+ LookupAllocationFunction(Sema::AFS_Global);
+ }
+
+ // If we found a non-aligned allocation function in the promise_type,
+ // it indicates the user forgot to update the allocation function. Let's emit
+ // a warning here.
+ if (FoundNonAlignedInPromise) {
+ S.Diag(OperatorNew->getLocation(),
+ diag::warn_non_aligned_allocation_function)
+ << &FD;
}
- if (!OperatorNew)
+ if (!OperatorNew) {
+ if (PromiseContainsNew)
+ S.Diag(Loc, diag::err_coroutine_unusable_new) << PromiseType << &FD;
+ else if (RequiresNoThrowAlloc)
+ S.Diag(Loc, diag::err_coroutine_unfound_nothrow_new)
+ << &FD << S.getLangOpts().CoroAlignedAllocation;
+
return false;
+ }
if (RequiresNoThrowAlloc) {
const auto *FT = OperatorNew->getType()->castAs<FunctionProtoType>();
@@ -1333,8 +1596,13 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
}
}
- if ((OperatorDelete = findDeleteForPromise(S, Loc, PromiseType)) == nullptr)
+ FunctionDecl *OperatorDelete = nullptr;
+ if (!findDeleteForPromise(S, Loc, PromiseType, OperatorDelete)) {
+ // FIXME: We should add an error here. According to:
+ // [dcl.fct.def.coroutine]p12
+ // If no usual deallocation function is found, the program is ill-formed.
return false;
+ }
Expr *FramePtr =
S.BuildBuiltinCallExpr(Loc, Builtin::BI__builtin_coro_frame, {});
@@ -1342,16 +1610,34 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
Expr *FrameSize =
S.BuildBuiltinCallExpr(Loc, Builtin::BI__builtin_coro_size, {});
- // Make new call.
+ Expr *FrameAlignment = nullptr;
+
+ if (S.getLangOpts().CoroAlignedAllocation) {
+ FrameAlignment =
+ S.BuildBuiltinCallExpr(Loc, Builtin::BI__builtin_coro_align, {});
+
+ TypeSourceInfo *AlignValTy = getTypeSourceInfoForStdAlignValT(S, Loc);
+ if (!AlignValTy)
+ return false;
+
+ FrameAlignment = S.BuildCXXNamedCast(Loc, tok::kw_static_cast, AlignValTy,
+ FrameAlignment, SourceRange(Loc, Loc),
+ SourceRange(Loc, Loc))
+ .get();
+ }
+ // Make new call.
ExprResult NewRef =
S.BuildDeclRefExpr(OperatorNew, OperatorNew->getType(), VK_LValue, Loc);
if (NewRef.isInvalid())
return false;
SmallVector<Expr *, 2> NewArgs(1, FrameSize);
- for (auto Arg : PlacementArgs)
- NewArgs.push_back(Arg);
+ if (S.getLangOpts().CoroAlignedAllocation && PassAlignment)
+ NewArgs.push_back(FrameAlignment);
+
+ if (OperatorNew->getNumParams() > NewArgs.size())
+ llvm::append_range(NewArgs, PlacementArgs);
ExprResult NewExpr =
S.BuildCallExpr(S.getCurScope(), NewRef.get(), Loc, NewArgs, Loc);
@@ -1373,12 +1659,36 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
SmallVector<Expr *, 2> DeleteArgs{CoroFree};
- // Check if we need to pass the size.
+ // [dcl.fct.def.coroutine]p12
+ // The selected deallocation function shall be called with the address of
+ // the block of storage to be reclaimed as its first argument. If a
+ // deallocation function with a parameter of type std::size_t is
+ // used, the size of the block is passed as the corresponding argument.
const auto *OpDeleteType =
OpDeleteQualType.getTypePtr()->castAs<FunctionProtoType>();
- if (OpDeleteType->getNumParams() > 1)
+ if (OpDeleteType->getNumParams() > DeleteArgs.size() &&
+ S.getASTContext().hasSameUnqualifiedType(
+ OpDeleteType->getParamType(DeleteArgs.size()), FrameSize->getType()))
DeleteArgs.push_back(FrameSize);
+ // Proposed Change of [dcl.fct.def.coroutine]p12 in P2014R0:
+ // If deallocation function lookup finds a usual deallocation function with
+ // a pointer parameter, size parameter and alignment parameter then this
+ // will be the selected deallocation function, otherwise if lookup finds a
+ // usual deallocation function with both a pointer parameter and a size
+ // parameter, then this will be the selected deallocation function.
+ // Otherwise, if lookup finds a usual deallocation function with only a
+ // pointer parameter, then this will be the selected deallocation
+ // function.
+ //
+ // So we are not forced to pass alignment to the deallocation function.
+ if (S.getLangOpts().CoroAlignedAllocation &&
+ OpDeleteType->getNumParams() > DeleteArgs.size() &&
+ S.getASTContext().hasSameUnqualifiedType(
+ OpDeleteType->getParamType(DeleteArgs.size()),
+ FrameAlignment->getType()))
+ DeleteArgs.push_back(FrameAlignment);
+
ExprResult DeleteExpr =
S.BuildCallExpr(S.getCurScope(), DeleteRef.get(), Loc, DeleteArgs, Loc);
DeleteExpr =
@@ -1396,9 +1706,13 @@ bool CoroutineStmtBuilder::makeOnFallthrough() {
assert(!IsPromiseDependentType &&
"cannot make statement while the promise type is dependent");
- // [dcl.fct.def.coroutine]/4
- // The unqualified-ids 'return_void' and 'return_value' are looked up in
- // the scope of class P. If both are found, the program is ill-formed.
+ // [dcl.fct.def.coroutine]/p6
+ // If searches for the names return_void and return_value in the scope of
+ // the promise type each find any declarations, the program is ill-formed.
+ // [Note 1: If return_void is found, flowing off the end of a coroutine is
+ // equivalent to a co_return with no operand. Otherwise, flowing off the end
+ // of a coroutine results in undefined behavior ([stmt.return.coroutine]). —
+ // end note]
bool HasRVoid, HasRValue;
LookupResult LRVoid =
lookupMember(S, "return_void", PromiseRecordDecl, Loc, HasRVoid);
@@ -1419,18 +1733,20 @@ bool CoroutineStmtBuilder::makeOnFallthrough() {
<< LRValue.getLookupName();
return false;
} else if (!HasRVoid && !HasRValue) {
- // FIXME: The PDTS currently specifies this case as UB, not ill-formed.
- // However we still diagnose this as an error since until the PDTS is fixed.
- S.Diag(FD.getLocation(),
- diag::err_coroutine_promise_requires_return_function)
- << PromiseRecordDecl;
- S.Diag(PromiseRecordDecl->getLocation(), diag::note_defined_here)
- << PromiseRecordDecl;
- return false;
+ // We need to set 'Fallthrough'. Otherwise the other analysis part might
+ // think the coroutine has defined a return_value method. So it might emit
+ // **false** positive warning. e.g.,
+ //
+ // promise_without_return_func foo() {
+ // co_await something();
+ // }
+ //
+ // Then AnalysisBasedWarning would emit a warning about `foo()` lacking a
+ // co_return statements, which isn't correct.
+ Fallthrough = S.ActOnNullStmt(PromiseRecordDecl->getLocation());
+ if (Fallthrough.isInvalid())
+ return false;
} else if (HasRVoid) {
- // If the unqualified-id return_void is found, flowing off the end of a
- // coroutine is equivalent to a co_return with no operand. Otherwise,
- // flowing off the end of a coroutine results in undefined behavior.
Fallthrough = S.BuildCoreturnStmt(FD.getLocation(), nullptr,
/*IsImplicit*/false);
Fallthrough = S.ActOnFinishFullStmt(Fallthrough.get());
@@ -1465,8 +1781,8 @@ bool CoroutineStmtBuilder::makeOnException() {
if (!S.getLangOpts().CXXExceptions)
return true;
- ExprResult UnhandledException = buildPromiseCall(S, Fn.CoroutinePromise, Loc,
- "unhandled_exception", None);
+ ExprResult UnhandledException = buildPromiseCall(
+ S, Fn.CoroutinePromise, Loc, "unhandled_exception", std::nullopt);
UnhandledException = S.ActOnFinishFullExpr(UnhandledException.get(), Loc,
/*DiscardedValue*/ false);
if (UnhandledException.isInvalid())
@@ -1486,10 +1802,11 @@ bool CoroutineStmtBuilder::makeOnException() {
}
bool CoroutineStmtBuilder::makeReturnObject() {
- // Build implicit 'p.get_return_object()' expression and form initialization
- // of return type from it.
- ExprResult ReturnObject =
- buildPromiseCall(S, Fn.CoroutinePromise, Loc, "get_return_object", None);
+ // [dcl.fct.def.coroutine]p7
+ // The expression promise.get_return_object() is used to initialize the
+ // returned reference or prvalue result object of a call to a coroutine.
+ ExprResult ReturnObject = buildPromiseCall(S, Fn.CoroutinePromise, Loc,
+ "get_return_object", std::nullopt);
if (ReturnObject.isInvalid())
return false;
@@ -1520,13 +1837,22 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
assert(!FnRetType->isDependentType() &&
"get_return_object type must no longer be dependent");
+ // The call to get_­return_­object is sequenced before the call to
+ // initial_­suspend and is invoked at most once, but there are caveats
+ // regarding on whether the prvalue result object may be initialized
+ // directly/eager or delayed, depending on the types involved.
+ //
+ // More info at https://github.com/cplusplus/papers/issues/1414
+ bool GroMatchesRetType = S.getASTContext().hasSameType(GroType, FnRetType);
+
if (FnRetType->isVoidType()) {
ExprResult Res =
S.ActOnFinishFullExpr(this->ReturnValue, Loc, /*DiscardedValue*/ false);
if (Res.isInvalid())
return false;
- this->ResultDecl = Res.get();
+ if (!GroMatchesRetType)
+ this->ResultDecl = Res.get();
return true;
}
@@ -1539,50 +1865,59 @@ bool CoroutineStmtBuilder::makeGroDeclAndReturnStmt() {
return false;
}
- auto *GroDecl = VarDecl::Create(
- S.Context, &FD, FD.getLocation(), FD.getLocation(),
- &S.PP.getIdentifierTable().get("__coro_gro"), GroType,
- S.Context.getTrivialTypeSourceInfo(GroType, Loc), SC_None);
- GroDecl->setImplicit();
+ StmtResult ReturnStmt;
+ clang::VarDecl *GroDecl = nullptr;
+ if (GroMatchesRetType) {
+ ReturnStmt = S.BuildReturnStmt(Loc, ReturnValue);
+ } else {
+ GroDecl = VarDecl::Create(
+ S.Context, &FD, FD.getLocation(), FD.getLocation(),
+ &S.PP.getIdentifierTable().get("__coro_gro"), GroType,
+ S.Context.getTrivialTypeSourceInfo(GroType, Loc), SC_None);
+ GroDecl->setImplicit();
+
+ S.CheckVariableDeclarationType(GroDecl);
+ if (GroDecl->isInvalidDecl())
+ return false;
- S.CheckVariableDeclarationType(GroDecl);
- if (GroDecl->isInvalidDecl())
- return false;
+ InitializedEntity Entity = InitializedEntity::InitializeVariable(GroDecl);
+ ExprResult Res =
+ S.PerformCopyInitialization(Entity, SourceLocation(), ReturnValue);
+ if (Res.isInvalid())
+ return false;
- InitializedEntity Entity = InitializedEntity::InitializeVariable(GroDecl);
- ExprResult Res =
- S.PerformCopyInitialization(Entity, SourceLocation(), ReturnValue);
- if (Res.isInvalid())
- return false;
+ Res = S.ActOnFinishFullExpr(Res.get(), /*DiscardedValue*/ false);
+ if (Res.isInvalid())
+ return false;
- Res = S.ActOnFinishFullExpr(Res.get(), /*DiscardedValue*/ false);
- if (Res.isInvalid())
- return false;
+ S.AddInitializerToDecl(GroDecl, Res.get(),
+ /*DirectInit=*/false);
- S.AddInitializerToDecl(GroDecl, Res.get(),
- /*DirectInit=*/false);
+ S.FinalizeDeclaration(GroDecl);
- S.FinalizeDeclaration(GroDecl);
+ // Form a declaration statement for the return declaration, so that AST
+ // visitors can more easily find it.
+ StmtResult GroDeclStmt =
+ S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(GroDecl), Loc, Loc);
+ if (GroDeclStmt.isInvalid())
+ return false;
- // Form a declaration statement for the return declaration, so that AST
- // visitors can more easily find it.
- StmtResult GroDeclStmt =
- S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(GroDecl), Loc, Loc);
- if (GroDeclStmt.isInvalid())
- return false;
+ this->ResultDecl = GroDeclStmt.get();
- this->ResultDecl = GroDeclStmt.get();
+ ExprResult declRef = S.BuildDeclRefExpr(GroDecl, GroType, VK_LValue, Loc);
+ if (declRef.isInvalid())
+ return false;
- ExprResult declRef = S.BuildDeclRefExpr(GroDecl, GroType, VK_LValue, Loc);
- if (declRef.isInvalid())
- return false;
+ ReturnStmt = S.BuildReturnStmt(Loc, declRef.get());
+ }
- StmtResult ReturnStmt = S.BuildReturnStmt(Loc, declRef.get());
if (ReturnStmt.isInvalid()) {
noteMemberDeclaredHere(S, ReturnValue, Fn);
return false;
}
- if (cast<clang::ReturnStmt>(ReturnStmt.get())->getNRVOCandidate() == GroDecl)
+
+ if (!GroMatchesRetType &&
+ cast<clang::ReturnStmt>(ReturnStmt.get())->getNRVOCandidate() == GroDecl)
GroDecl->setNRVOVariable(true);
this->ReturnStmt = ReturnStmt.get();
@@ -1625,13 +1960,25 @@ bool Sema::buildCoroutineParameterMoves(SourceLocation Loc) {
if (!ScopeInfo->CoroutineParameterMoves.empty())
return false;
+ // [dcl.fct.def.coroutine]p13
+ // When a coroutine is invoked, after initializing its parameters
+ // ([expr.call]), a copy is created for each coroutine parameter. For a
+ // parameter of type cv T, the copy is a variable of type cv T with
+ // automatic storage duration that is direct-initialized from an xvalue of
+ // type T referring to the parameter.
for (auto *PD : FD->parameters()) {
if (PD->getType()->isDependentType())
continue;
+ // Preserve the referenced state for unused parameter diagnostics.
+ bool DeclReferenced = PD->isReferenced();
+
ExprResult PDRefExpr =
BuildDeclRefExpr(PD, PD->getType().getNonReferenceType(),
ExprValueKind::VK_LValue, Loc); // FIXME: scope?
+
+ PD->setReferenced(DeclReferenced);
+
if (PDRefExpr.isInvalid())
return false;
@@ -1641,8 +1988,10 @@ bool Sema::buildCoroutineParameterMoves(SourceLocation Loc) {
CExpr = castForMoving(*this, PDRefExpr.get());
else
CExpr = PDRefExpr.get();
-
- auto D = buildVarDecl(*this, Loc, PD->getType(), PD->getIdentifier());
+ // [dcl.fct.def.coroutine]p13
+ // The initialization and destruction of each parameter copy occurs in the
+ // context of the called coroutine.
+ auto *D = buildVarDecl(*this, Loc, PD->getType(), PD->getIdentifier());
AddInitializerToDecl(D, CExpr, /*DirectInit=*/true);
// Convert decl to a statement.
@@ -1664,24 +2013,31 @@ StmtResult Sema::BuildCoroutineBodyStmt(CoroutineBodyStmt::CtorArgs Args) {
ClassTemplateDecl *Sema::lookupCoroutineTraits(SourceLocation KwLoc,
SourceLocation FuncLoc) {
+ if (StdCoroutineTraitsCache)
+ return StdCoroutineTraitsCache;
+
+ IdentifierInfo const &TraitIdent =
+ PP.getIdentifierTable().get("coroutine_traits");
+
+ NamespaceDecl *StdSpace = getStdNamespace();
+ LookupResult Result(*this, &TraitIdent, FuncLoc, LookupOrdinaryName);
+ bool Found = StdSpace && LookupQualifiedName(Result, StdSpace);
+
+ if (!Found) {
+ // The goggles, we found nothing!
+ Diag(KwLoc, diag::err_implied_coroutine_type_not_found)
+ << "std::coroutine_traits";
+ return nullptr;
+ }
+
+ // coroutine_traits is required to be a class template.
+ StdCoroutineTraitsCache = Result.getAsSingle<ClassTemplateDecl>();
if (!StdCoroutineTraitsCache) {
- if (auto StdExp = lookupStdExperimentalNamespace()) {
- LookupResult Result(*this,
- &PP.getIdentifierTable().get("coroutine_traits"),
- FuncLoc, LookupOrdinaryName);
- if (!LookupQualifiedName(Result, StdExp)) {
- Diag(KwLoc, diag::err_implied_coroutine_type_not_found)
- << "std::experimental::coroutine_traits";
- return nullptr;
- }
- if (!(StdCoroutineTraitsCache =
- Result.getAsSingle<ClassTemplateDecl>())) {
- Result.suppressDiagnostics();
- NamedDecl *Found = *Result.begin();
- Diag(Found->getLocation(), diag::err_malformed_std_coroutine_traits);
- return nullptr;
- }
- }
+ Result.suppressDiagnostics();
+ NamedDecl *Found = *Result.begin();
+ Diag(Found->getLocation(), diag::err_malformed_std_coroutine_traits);
+ return nullptr;
}
+
return StdCoroutineTraitsCache;
}