aaronpuchert updated this revision to Diff 225149. aaronpuchert added a comment.
Apply clang-format. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D68845/new/ https://reviews.llvm.org/D68845 Files: clang/lib/Sema/SemaCoroutine.cpp clang/test/SemaCXX/coroutine-rvo.cpp
Index: clang/test/SemaCXX/coroutine-rvo.cpp =================================================================== --- clang/test/SemaCXX/coroutine-rvo.cpp +++ clang/test/SemaCXX/coroutine-rvo.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -stdlib=libc++ -std=c++1z -fcoroutines-ts -fsyntax-only +// RUN: %clang_cc1 -verify -std=c++17 -fcoroutines-ts -fsyntax-only %s namespace std::experimental { template <class Promise = void> struct coroutine_handle { @@ -39,10 +39,14 @@ }; struct MoveOnly { - MoveOnly() {}; + MoveOnly() = default; MoveOnly(const MoveOnly&) = delete; - MoveOnly(MoveOnly&&) noexcept {}; - ~MoveOnly() {}; + MoveOnly(MoveOnly &&) = default; +}; + +struct NoCopyNoMove { + NoCopyNoMove() = default; + NoCopyNoMove(const NoCopyNoMove &) = delete; }; template <typename T> @@ -52,18 +56,93 @@ auto final_suspend() { return suspend_never{}; } auto get_return_object() { return task{}; } static void unhandled_exception() {} - void return_value(T&& value) {} + void return_value(T &&value) {} // expected-note 2{{passing argument}} }; }; -task<MoveOnly> f() { - MoveOnly value; +task<NoCopyNoMove> local2val() { + NoCopyNoMove value; + co_return value; +} + +task<NoCopyNoMove &> local2ref() { + NoCopyNoMove value; + co_return value; +} + +// We need the move constructor for construction of the coroutine. +task<MoveOnly> param2val(MoveOnly value) { + co_return value; +} + +task<NoCopyNoMove> lvalue2val(NoCopyNoMove &value) { + co_return value; // expected-error{{rvalue reference to type 'NoCopyNoMove' cannot bind to lvalue of type 'NoCopyNoMove'}} +} + +task<NoCopyNoMove> rvalue2val(NoCopyNoMove &&value) { + co_return value; +} + +task<NoCopyNoMove &> lvalue2ref(NoCopyNoMove &value) { co_return value; } -int main() { - f(); - return 0; +task<NoCopyNoMove &> rvalue2ref(NoCopyNoMove &&value) { + co_return value; +} + +struct To { + operator MoveOnly() &&; +}; +task<MoveOnly> conversion_operator() { + To t; + co_return t; +} + +struct Construct { + Construct(MoveOnly); +}; +task<Construct> converting_constructor() { + MoveOnly w; + co_return w; +} + +struct Derived : MoveOnly {}; +task<MoveOnly> derived2base() { + Derived result; + co_return result; +} + +struct RetThis { + task<RetThis> foo() && { + co_return *this; // expected-error{{rvalue reference to type 'RetThis' cannot bind to lvalue of type 'RetThis'}} + } +}; + +template <typename, typename> +struct is_same { static constexpr bool value = false; }; + +template <typename T> +struct is_same<T, T> { static constexpr bool value = true; }; + +template <typename T> +struct template_return_task { + struct promise_type { + auto initial_suspend() { return suspend_never{}; } + auto final_suspend() { return suspend_never{}; } + auto get_return_object() { return template_return_task{}; } + static void unhandled_exception(); + template <typename U> + void return_value(U &&value) { + static_assert(is_same<T, U>::value); + } + }; +}; + +template_return_task<MoveOnly> param2template(MoveOnly value) { + co_return value; // We should deduce U = MoveOnly. } -// expected-no-diagnostics +template_return_task<NoCopyNoMove &> lvalue2template(NoCopyNoMove &value) { + co_return value; // We should deduce U = NoCopyNoMove&. +} Index: clang/lib/Sema/SemaCoroutine.cpp =================================================================== --- clang/lib/Sema/SemaCoroutine.cpp +++ clang/lib/Sema/SemaCoroutine.cpp @@ -851,6 +851,72 @@ return BuildCoreturnStmt(Loc, E); } +// [class.copy.elision]p3: If the expression in a [...] co_return statement +// is a (possibly parenthesized) id-expression that names an implicitly +// movable entity declared in the body or parameter-declaration-clause of +// the innermost enclosing function or lambda-expression, [...] overload +// resolution to select [...] the return_value overload to call is first +// performed as if the expression or operand were an rvalue. If the first +// overload resolution fails or was not performed, overload resolution is +// performed again, considering the expression or operand as an lvalue. +static bool shouldMoveReturnExpr(Sema &S, RecordDecl *PromiseTypeDecl, + Expr *E) { + // Check if we're allowed to implicitly move. + VarDecl *ImplicitMoveCandidate = + S.getCopyElisionCandidate(E->getType(), E, Sema::CES_Default); + if (!ImplicitMoveCandidate) + return false; + if (ImplicitMoveCandidate->getType()->isLValueReferenceType()) + return false; + + ImplicitCastExpr AsRvalue(ImplicitCastExpr::OnStack, E->getType(), CK_NoOp, E, + VK_XValue); + + // Lookup return_value methods in the promise type, and check if any accepts + // the moved expression. If it does, we do the move. + DeclarationNameInfo NameInfo(&S.PP.getIdentifierTable().get("return_value"), + SourceLocation{}); + LookupResult LR(S, NameInfo, Sema::LookupMemberName); + CXXScopeSpec SS; + S.LookupQualifiedName(LR, PromiseTypeDecl, SS); + for (NamedDecl *D : LR) { + if (const auto *USD = dyn_cast<UsingShadowDecl>(D)) + D = USD->getTargetDecl(); + if (const auto *CMD = dyn_cast<CXXMethodDecl>(D)) { + if (CMD->isDeleted()) + continue; + if (CMD->getNumParams() < 1) + continue; + for (unsigned param = 1; param < CMD->getNumParams(); ++param) + if (!CMD->getParamDecl(param)->hasDefaultArg()) + continue; + InitializedEntity Entity = InitializedEntity::InitializeParameter( + S.Context, CMD->getParamDecl(0)); + if (S.CanPerformCopyInitialization(Entity, &AsRvalue)) + return true; + } else if (auto *FTD = dyn_cast<FunctionTemplateDecl>(D)) { + TemplateDeductionInfo Info(SourceLocation{}); + FunctionDecl *Specialization = nullptr; + Sema::TemplateDeductionResult Result = S.DeduceTemplateArguments( + FTD, {}, {&AsRvalue}, Specialization, Info, false, + [&](ArrayRef<QualType> ParamTypes) { + QualType ParamType = ParamTypes[0]; + if (!ParamType->isDependentType()) { + InitializedEntity Entity = InitializedEntity::InitializeParameter( + S.Context, ParamType, false); + if (!S.CanPerformCopyInitialization(Entity, &AsRvalue)) + return true; + } + return false; + }); + if (Result == Sema::TDK_Success) + return true; + } + // TODO: handle VarDecls. They can be function pointers or function objects. + } + return false; +} + StmtResult Sema::BuildCoreturnStmt(SourceLocation Loc, Expr *E, bool IsImplicit) { auto *FSI = checkCoroutineContext(*this, Loc, "co_return", IsImplicit); @@ -864,26 +930,15 @@ E = R.get(); } - // Move the return value if we can - if (E) { - auto NRVOCandidate = this->getCopyElisionCandidate(E->getType(), E, CES_AsIfByStdMove); - if (NRVOCandidate) { - InitializedEntity Entity = - InitializedEntity::InitializeResult(Loc, E->getType(), NRVOCandidate); - ExprResult MoveResult = this->PerformMoveOrCopyInitialization( - Entity, NRVOCandidate, E->getType(), E); - if (MoveResult.get()) - E = MoveResult.get(); - } - } - - // FIXME: If the operand is a reference to a variable that's about to go out - // of scope, we should treat the operand as an xvalue for this overload - // resolution. VarDecl *Promise = FSI->CoroutinePromise; ExprResult PC; if (E && (isa<InitListExpr>(E) || !E->getType()->isVoidType())) { - PC = buildPromiseCall(*this, Promise, Loc, "return_value", E); + Expr *Arg = E; + if (const RecordType *PromiseT = Promise->getType()->getAs<RecordType>()) + if (shouldMoveReturnExpr(*this, PromiseT->getDecl(), E)) + Arg = ImplicitCastExpr::Create(Context, E->getType(), CK_NoOp, E, + nullptr, VK_XValue); + PC = buildPromiseCall(*this, Promise, Loc, "return_value", Arg); } else { E = MakeFullDiscardedValueExpr(E).get(); PC = buildPromiseCall(*this, Promise, Loc, "return_void", None);
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits