diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3427,12 +3427,6 @@ bool DiagnoseMultipleUserDefinedConversion(Expr *From, QualType ToType); bool isSameOrCompatibleFunctionType(CanQualType Param, CanQualType Arg); - ExprResult PerformMoveOrCopyInitialization(const InitializedEntity &Entity, - const VarDecl *NRVOCandidate, - QualType ResultType, - Expr *Value, - bool AllowNRVO = true); - bool CanPerformAggregateInitializationForOverloadResolution( const InitializedEntity &Entity, InitListExpr *From); @@ -4729,28 +4723,30 @@ SourceLocation Loc, unsigned NumParams); - enum CopyElisionSemanticsKind { - CES_Strict = 0, - CES_AllowParameters = 1, - CES_AllowDifferentTypes = 2, - CES_AllowExceptionVariables = 4, - CES_AllowRValueReferenceType = 8, - CES_ImplicitlyMovableCXX11CXX14CXX17 = - (CES_AllowParameters | CES_AllowDifferentTypes), - CES_ImplicitlyMovableCXX20 = - (CES_AllowParameters | CES_AllowDifferentTypes | - CES_AllowExceptionVariables | CES_AllowRValueReferenceType), + struct NRVOResult { + const VarDecl *Candidate; + + enum Status : int8_t { None, MoveEligible, MoveEligibleAndCopyElidable }; + Status S; + bool isParenthesized; + + bool isMoveEligible() const { return S >= MoveEligible; }; + bool isCopyElidable() const { return S == MoveEligibleAndCopyElidable; } }; + NRVOResult getNRVOResult(const Expr *E, bool ForceCXX20 = false); + NRVOResult getNRVOResult(const VarDecl *VD, bool Parenthesized = false, + bool ForceCXX20 = false); + void updNRVOResultWithRetType(NRVOResult &Res, QualType ReturnType); - VarDecl *getCopyElisionCandidate(QualType ReturnType, Expr *E, - CopyElisionSemanticsKind CESK); - bool isCopyElisionCandidate(QualType ReturnType, const VarDecl *VD, - CopyElisionSemanticsKind CESK); + ExprResult PerformMoveOrCopyInitialization(const InitializedEntity &Entity, + const NRVOResult &NRVORes, + Expr *Value); StmtResult ActOnReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, Scope *CurScope); StmtResult BuildReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp); - StmtResult ActOnCapScopeReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp); + StmtResult ActOnCapScopeReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp, + NRVOResult &NRVORes); StmtResult ActOnGCCAsmStmt(SourceLocation AsmLoc, bool IsSimple, bool IsVolatile, unsigned NumOutputs, diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1952,9 +1952,10 @@ SourceLocation Loc = VD->getLocation(); Expr *VarRef = new (S.Context) DeclRefExpr(S.Context, VD, false, T, VK_LValue, Loc); - ExprResult Result = S.PerformMoveOrCopyInitialization( - InitializedEntity::InitializeBlock(Loc, T, false), VD, VD->getType(), - VarRef, /*AllowNRVO=*/true); + ExprResult Result = S.PerformCopyInitialization( + InitializedEntity::InitializeBlock(Loc, T, false), SourceLocation(), + VarRef); + if (!Result.isInvalid()) { Result = S.MaybeCreateExprWithCleanups(Result); Expr *Init = Result.getAs(); diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -995,17 +995,13 @@ } // Move the return value if we can - if (E) { - const VarDecl *NRVOCandidate = this->getCopyElisionCandidate( - E->getType(), E, CES_ImplicitlyMovableCXX20); - 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(); - } + NRVOResult NRVORes = getNRVOResult(E, /*ForceCXX20=*/true); + if (NRVORes.isMoveEligible()) { + InitializedEntity Entity = InitializedEntity::InitializeResult( + Loc, E->getType(), NRVORes.Candidate); + ExprResult MoveResult = PerformMoveOrCopyInitialization(Entity, NRVORes, E); + if (MoveResult.get()) + E = MoveResult.get(); } // FIXME: If the operand is a reference to a variable that's about to go out @@ -1570,7 +1566,7 @@ // Trigger a nice error message. InitializedEntity Entity = InitializedEntity::InitializeResult(Loc, FnRetType, false); - S.PerformMoveOrCopyInitialization(Entity, nullptr, FnRetType, ReturnValue); + S.PerformCopyInitialization(Entity, SourceLocation(), ReturnValue); noteMemberDeclaredHere(S, ReturnValue, Fn); return false; } @@ -1586,8 +1582,8 @@ return false; InitializedEntity Entity = InitializedEntity::InitializeVariable(GroDecl); - ExprResult Res = S.PerformMoveOrCopyInitialization(Entity, nullptr, GroType, - this->ReturnValue); + ExprResult Res = + S.PerformCopyInitialization(Entity, SourceLocation(), ReturnValue); if (Res.isInvalid()) return false; diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -851,6 +851,8 @@ Diag(OpLoc, diag::err_omp_simd_region_cannot_use_stmt) << "throw"; if (Ex && !Ex->isTypeDependent()) { + NRVOResult NRVORes = IsThrownVarInScope ? getNRVOResult(Ex) : NRVOResult(); + QualType ExceptionObjectTy = Context.getExceptionObjectType(Ex->getType()); if (CheckCXXThrowOperand(OpLoc, ExceptionObjectTy, Ex)) return ExprError(); @@ -870,15 +872,11 @@ // operation from the operand to the exception object (15.1) can be // omitted by constructing the automatic object directly into the // exception object - const VarDecl *NRVOVariable = nullptr; - if (IsThrownVarInScope) - NRVOVariable = getCopyElisionCandidate(QualType(), Ex, CES_Strict); InitializedEntity Entity = InitializedEntity::InitializeException( OpLoc, ExceptionObjectTy, - /*NRVO=*/NRVOVariable != nullptr); - ExprResult Res = PerformMoveOrCopyInitialization( - Entity, NRVOVariable, QualType(), Ex, IsThrownVarInScope); + /*NRVO=*/NRVORes.isCopyElidable()); + ExprResult Res = PerformMoveOrCopyInitialization(Entity, NRVORes, Ex); if (Res.isInvalid()) return ExprError(); Ex = Res.get(); diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -3026,99 +3026,155 @@ return new (Context) BreakStmt(BreakLoc); } -/// Determine whether the given expression is a candidate for -/// copy elision in either a return statement or a throw expression. +static void downgradeNRVOResult(Sema::NRVOResult &Res, bool CanMove) { + Res.S = Res.S != Sema::NRVOResult::None && CanMove + ? Sema::NRVOResult::MoveEligible + : Sema::NRVOResult::None; +} + +/// Determine whether the given NRVO candidate variable is move-eligible or +/// copy-elidable, without considering function return type. /// -/// \param ReturnType If we're determining the copy elision candidate for -/// a return statement, this is the return type of the function. If we're -/// determining the copy elision candidate for a throw expression, this will -/// be a NULL type. +/// \param VD The NRVO candidate variable. /// -/// \param E The expression being returned from the function or block, or -/// being thrown. +/// \\param Parenthesized Whether the expression this candidate was obtained +/// from was parenthesized. /// -/// \param CESK Whether we allow function parameters or -/// id-expressions that could be moved out of the function to be considered NRVO -/// candidates. C++ prohibits these for NRVO itself, but we re-use this logic to -/// determine whether we should try to move as part of a return or throw (which -/// does allow function parameters). +/// \param ForceCXX20 Overrides detection of current language mode +/// and uses the rules for C++20. /// -/// \returns The NRVO candidate variable, if the return statement may use the -/// NRVO, or NULL if there is no such candidate. -VarDecl *Sema::getCopyElisionCandidate(QualType ReturnType, Expr *E, - CopyElisionSemanticsKind CESK) { - // - in a return statement in a function [where] ... - // ... the expression is the name of a non-volatile automatic object ... - DeclRefExpr *DR = dyn_cast(E->IgnoreParens()); - if (!DR || DR->refersToEnclosingVariableOrCapture()) - return nullptr; - VarDecl *VD = dyn_cast(DR->getDecl()); +/// \returns An aggregate which contains the Candidate and isMoveEligible +/// and isCopyElidable methods. If Candidate is non-null, it means +/// isMoveEligible() would be true under the most permissive language standard. +Sema::NRVOResult Sema::getNRVOResult(const VarDecl *VD, bool Parenthesized, + bool ForceCXX20) { if (!VD) - return nullptr; + return NRVOResult(); - if (isCopyElisionCandidate(ReturnType, VD, CESK)) - return VD; - return nullptr; -} + bool hasCXX11 = getLangOpts().CPlusPlus11 || ForceCXX20, + hasCXX20 = getLangOpts().CPlusPlus20 || ForceCXX20; + NRVOResult Res{VD, NRVOResult::MoveEligibleAndCopyElidable, Parenthesized}; -bool Sema::isCopyElisionCandidate(QualType ReturnType, const VarDecl *VD, - CopyElisionSemanticsKind CESK) { - QualType VDType = VD->getType(); - // - in a return statement in a function with ... - // ... a class return type ... - if (!ReturnType.isNull() && !ReturnType->isDependentType()) { - if (!ReturnType->isRecordType()) - return false; - // ... the same cv-unqualified type as the function return type ... - // When considering moving this expression out, allow dissimilar types. - if (!(CESK & CES_AllowDifferentTypes) && !VDType->isDependentType() && - !Context.hasSameUnqualifiedType(ReturnType, VDType)) - return false; + // (other than a function ... parameter) + if (VD->getKind() == Decl::ParmVar) { + downgradeNRVOResult(Res, hasCXX11); + } else if (VD->getKind() != Decl::Var) { + return NRVOResult(); } - // ...object (other than a function or catch-clause parameter)... - if (VD->getKind() != Decl::Var && - !((CESK & CES_AllowParameters) && VD->getKind() == Decl::ParmVar)) - return false; - if (!(CESK & CES_AllowExceptionVariables) && VD->isExceptionVariable()) - return false; + // (other than ... a catch-clause parameter) + if (VD->isExceptionVariable()) + downgradeNRVOResult(Res, hasCXX20); // ...automatic... - if (!VD->hasLocalStorage()) return false; + if (!VD->hasLocalStorage()) + return NRVOResult(); // Return false if VD is a __block variable. We don't want to implicitly move // out of a __block variable during a return because we cannot assume the // variable will no longer be used. if (VD->hasAttr()) - return false; + return NRVOResult(); + QualType VDType = VD->getType(); if (VDType->isObjectType()) { // C++17 [class.copy.elision]p3: // ...non-volatile automatic object... if (VDType.isVolatileQualified()) - return false; + return NRVOResult(); } else if (VDType->isRValueReferenceType()) { // C++20 [class.copy.elision]p3: - // ...either a non-volatile object or an rvalue reference to a non-volatile object type... - if (!(CESK & CES_AllowRValueReferenceType)) - return false; + // ...either a non-volatile object or an rvalue reference to a non-volatile + // object type... QualType VDReferencedType = VDType.getNonReferenceType(); - if (VDReferencedType.isVolatileQualified() || !VDReferencedType->isObjectType()) - return false; + if (VDReferencedType.isVolatileQualified() || + !VDReferencedType->isObjectType()) + return NRVOResult(); + downgradeNRVOResult(Res, hasCXX20); } else { - return false; + return NRVOResult(); } - if (CESK & CES_AllowDifferentTypes) - return true; - // Variables with higher required alignment than their type's ABI // alignment cannot use NRVO. if (!VDType->isDependentType() && VD->hasAttr() && Context.getDeclAlign(VD) > Context.getTypeAlignInChars(VDType)) - return false; + downgradeNRVOResult(Res, hasCXX11); - return true; + return Res; +} + +/// Determine whether the given expression might be move-eligible or +/// copy-elidable in either a (co_)return statement or throw expression, +/// without considering function return type, if applicable. +/// +/// \param E The expression being returned from the function or block, +/// being thrown, or being co_returned from a coroutine. +/// +/// \param ForceCXX20 Overrides detection of current language mode +/// and uses the rules for C++20. +/// +/// \returns An aggregate which contains the Candidate and isMoveEligible +/// and isCopyElidable methods. If Candidate is non-null, it means +/// isMoveEligible() would be true under the most permissive language standard. +Sema::NRVOResult Sema::getNRVOResult(const Expr *E, bool ForceCXX20) { + if (!E) + return NRVOResult(); + bool Parenthesized = isa(E); + // - in a return statement in a function [where] ... + // ... the expression is the name of a non-volatile automatic object ... + const auto *DR = dyn_cast(E->IgnoreParens()); + if (!DR || DR->refersToEnclosingVariableOrCapture()) + return NRVOResult(); + return getNRVOResult(dyn_cast(DR->getDecl()), Parenthesized, + ForceCXX20); +} + +/// Updates given NRVOResult's move-eligible and +/// copy-elidable statuses, considering the function return type criteria +/// as applicable to return statements. +/// +/// \param Res The NRVOResult object to update. +/// +/// \param ReturnType This is the return type of the function. +void Sema::updNRVOResultWithRetType(NRVOResult &Res, QualType ReturnType) { + if (!Res.Candidate) + return; + + // Discard candidates from functions where the return type is any kind of + // reference including auto&, and T& for some dependent T. + // TODO: Can discard many other auto / dependent types which can never be a + // record type. + if (ReturnType->isReferenceType()) + return Res = NRVOResult(), void(); + + QualType VDType = Res.Candidate->getType(); + if (AutoType *AT = ReturnType->getContainedAutoType()) { + if (AT->isCanonicalUnqualified()) { + // The return type is some kind of undeduced auto. + // If is decltype(auto) and the original return expression + // was parethesized, then we can discard this candidate because + // it would deduce to a reference. + if (AT->isDecltypeAuto() && Res.isParenthesized) + return Res = NRVOResult(), void(); + // Otherwise, we consider the return type would deduce to + // the variable type. + ReturnType = VDType; + } + } + + if (ReturnType->isDependentType()) + return; + // - in a return statement in a function with ... + // ... a class return type ... + if (!ReturnType->isRecordType()) + return Res = NRVOResult(), void(); + + // ... the same cv-unqualified type as the function return type ... + // When considering moving this expression out, allow dissimilar types. + if (!VDType->isDependentType() && + !Context.hasSameUnqualifiedType(ReturnType, VDType)) + downgradeNRVOResult(Res, getLangOpts().CPlusPlus11); } /// Try to perform the initialization of a potentially-movable value, @@ -3143,8 +3199,7 @@ /// the selected constructor/operator doesn't match the additional criteria, we /// need to do the second overload resolution. static bool TryMoveInitialization(Sema &S, const InitializedEntity &Entity, - const VarDecl *NRVOCandidate, - QualType ResultType, Expr *&Value, + const VarDecl *NRVOCandidate, Expr *&Value, bool ConvertingConstructorsOnly, bool IsDiagnosticsCheck, ExprResult &Res) { ImplicitCastExpr AsRvalue(ImplicitCastExpr::OnStack, Value->getType(), @@ -3227,63 +3282,44 @@ /// This routine implements C++20 [class.copy.elision]p3, which attempts to /// treat returned lvalues as rvalues in certain cases (to prefer move /// construction), then falls back to treating them as lvalues if that failed. -ExprResult Sema::PerformMoveOrCopyInitialization( - const InitializedEntity &Entity, const VarDecl *NRVOCandidate, - QualType ResultType, Expr *Value, bool AllowNRVO) { - ExprResult Res = ExprError(); - bool NeedSecondOverloadResolution = true; - - if (AllowNRVO) { - CopyElisionSemanticsKind CESK = CES_Strict; - if (getLangOpts().CPlusPlus20) { - CESK = CES_ImplicitlyMovableCXX20; - } else if (getLangOpts().CPlusPlus11) { - CESK = CES_ImplicitlyMovableCXX11CXX14CXX17; - } - - if (!NRVOCandidate) { - NRVOCandidate = getCopyElisionCandidate(ResultType, Value, CESK); - } - - if (NRVOCandidate) { - NeedSecondOverloadResolution = - TryMoveInitialization(*this, Entity, NRVOCandidate, ResultType, Value, - !getLangOpts().CPlusPlus20, false, Res); +ExprResult +Sema::PerformMoveOrCopyInitialization(const InitializedEntity &Entity, + const NRVOResult &NRVORes, Expr *Value) { + + if (NRVORes.Candidate) { + if (NRVORes.isMoveEligible()) { + ExprResult Res; + if (!TryMoveInitialization(*this, Entity, NRVORes.Candidate, Value, + !getLangOpts().CPlusPlus20, false, Res)) + return Res; } - - if (!getLangOpts().CPlusPlus20 && NeedSecondOverloadResolution && - !getDiagnostics().isIgnored(diag::warn_return_std_move, + if (!getDiagnostics().isIgnored(diag::warn_return_std_move, Value->getExprLoc())) { - const VarDecl *FakeNRVOCandidate = getCopyElisionCandidate( - QualType(), Value, CES_ImplicitlyMovableCXX20); - if (FakeNRVOCandidate) { - QualType QT = FakeNRVOCandidate->getType(); - if (QT->isLValueReferenceType()) { - // Adding 'std::move' around an lvalue reference variable's name is - // dangerous. Don't suggest it. - } else if (QT.getNonReferenceType() - .getUnqualifiedType() - .isTriviallyCopyableType(Context)) { - // Adding 'std::move' around a trivially copyable variable is probably - // pointless. Don't suggest it. - } else { - ExprResult FakeRes = ExprError(); - Expr *FakeValue = Value; - TryMoveInitialization(*this, Entity, FakeNRVOCandidate, ResultType, - FakeValue, false, true, FakeRes); - if (!FakeRes.isInvalid()) { - bool IsThrow = - (Entity.getKind() == InitializedEntity::EK_Exception); - SmallString<32> Str; - Str += "std::move("; - Str += FakeNRVOCandidate->getDeclName().getAsString(); - Str += ")"; - Diag(Value->getExprLoc(), diag::warn_return_std_move) - << Value->getSourceRange() - << FakeNRVOCandidate->getDeclName() << IsThrow; - Diag(Value->getExprLoc(), diag::note_add_std_move) - << FixItHint::CreateReplacement(Value->getSourceRange(), Str); - } + QualType QT = NRVORes.Candidate->getType(); + if (QT->isLValueReferenceType()) { + // Adding 'std::move' around an lvalue reference variable's name is + // dangerous. Don't suggest it. + } else if (QT.getNonReferenceType() + .getUnqualifiedType() + .isTriviallyCopyableType(Context)) { + // Adding 'std::move' around a trivially copyable variable is probably + // pointless. Don't suggest it. + } else { + ExprResult FakeRes = ExprError(); + Expr *FakeValue = Value; + TryMoveInitialization(*this, Entity, NRVORes.Candidate, FakeValue, + false, true, FakeRes); + if (!FakeRes.isInvalid()) { + bool IsThrow = (Entity.getKind() == InitializedEntity::EK_Exception); + SmallString<32> Str; + Str += "std::move("; + Str += NRVORes.Candidate->getDeclName().getAsString(); + Str += ")"; + Diag(Value->getExprLoc(), diag::warn_return_std_move) + << Value->getSourceRange() << NRVORes.Candidate->getDeclName() + << IsThrow; + Diag(Value->getExprLoc(), diag::note_add_std_move) + << FixItHint::CreateReplacement(Value->getSourceRange(), Str); } } } @@ -3292,10 +3328,7 @@ // Either we didn't meet the criteria for treating an lvalue as an rvalue, // above, or overload resolution failed. Either way, we need to try // (again) now with the return value expression as written. - if (NeedSecondOverloadResolution) - Res = PerformCopyInitialization(Entity, SourceLocation(), Value); - - return Res; + return PerformCopyInitialization(Entity, SourceLocation(), Value); } /// Determine whether the declared return type of the specified function @@ -3309,8 +3342,8 @@ /// ActOnCapScopeReturnStmt - Utility routine to type-check return statements /// for capturing scopes. /// -StmtResult -Sema::ActOnCapScopeReturnStmt(SourceLocation ReturnLoc, Expr *RetValExp) { +StmtResult Sema::ActOnCapScopeReturnStmt(SourceLocation ReturnLoc, + Expr *RetValExp, NRVOResult &NRVORes) { // If this is the first return we've seen, infer the return type. // [expr.prim.lambda]p4 in C++11; block literals follow the same rules. CapturingScopeInfo *CurCap = cast(getCurFunction()); @@ -3389,7 +3422,7 @@ if (CurCap->ReturnType.isNull()) CurCap->ReturnType = FnRetType; } - assert(!FnRetType.isNull()); + updNRVOResultWithRetType(NRVORes, FnRetType); if (auto *CurBlock = dyn_cast(CurCap)) { if (CurBlock->FunctionType->castAs()->getNoReturnAttr()) { @@ -3412,7 +3445,6 @@ // Otherwise, verify that this result type matches the previous one. We are // pickier with blocks than for normal functions because we don't have GCC // compatibility to worry about here. - const VarDecl *NRVOCandidate = nullptr; if (FnRetType->isDependentType()) { // Delay processing for now. TODO: there are lots of dependent // types we can conclusively prove aren't void. @@ -3440,20 +3472,16 @@ // In C++ the return statement is handled via a copy initialization. // the C version of which boils down to CheckSingleAssignmentConstraints. - NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, CES_Strict); - InitializedEntity Entity = InitializedEntity::InitializeResult(ReturnLoc, - FnRetType, - NRVOCandidate != nullptr); - ExprResult Res = PerformMoveOrCopyInitialization(Entity, NRVOCandidate, - FnRetType, RetValExp); + InitializedEntity Entity = InitializedEntity::InitializeResult( + ReturnLoc, FnRetType, NRVORes.isCopyElidable()); + ExprResult Res = + PerformMoveOrCopyInitialization(Entity, NRVORes, RetValExp); if (Res.isInvalid()) { // FIXME: Cleanup temporaries here, anyway? return StmtError(); } RetValExp = Res.get(); CheckReturnValExpr(RetValExp, FnRetType, ReturnLoc); - } else { - NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, CES_Strict); } if (RetValExp) { @@ -3463,13 +3491,14 @@ return StmtError(); RetValExp = ER.get(); } - auto *Result = - ReturnStmt::Create(Context, ReturnLoc, RetValExp, NRVOCandidate); + auto *Result = ReturnStmt::Create(Context, ReturnLoc, RetValExp, + NRVORes.isCopyElidable() ? NRVORes.Candidate + : nullptr); // If we need to check for the named return value optimization, // or if we need to infer the return type, // save the return statement in our scope for later processing. - if (CurCap->HasImplicitReturnType || NRVOCandidate) + if (CurCap->HasImplicitReturnType || NRVORes.isCopyElidable()) FunctionScopes.back()->Returns.push_back(Result); if (FunctionScopes.back()->FirstReturnLoc.isInvalid()) @@ -3662,8 +3691,10 @@ if (RetValExp && DiagnoseUnexpandedParameterPack(RetValExp)) return StmtError(); + NRVOResult NRVORes = getNRVOResult(RetValExp); + if (isa(getCurFunction())) - return ActOnCapScopeReturnStmt(ReturnLoc, RetValExp); + return ActOnCapScopeReturnStmt(ReturnLoc, RetValExp, NRVORes); QualType FnRetType; QualType RelatedRetType; @@ -3735,6 +3766,7 @@ } } } + updNRVOResultWithRetType(NRVORes, FnRetType); bool HasDependentReturnType = FnRetType->isDependentType(); @@ -3841,8 +3873,6 @@ /* NRVOCandidate=*/nullptr); } else { assert(RetValExp || HasDependentReturnType); - const VarDecl *NRVOCandidate = nullptr; - QualType RetType = RelatedRetType.isNull() ? FnRetType : RelatedRetType; // C99 6.8.6.4p3(136): The return statement is not an assignment. The @@ -3851,15 +3881,12 @@ // In C++ the return statement is handled via a copy initialization, // the C version of which boils down to CheckSingleAssignmentConstraints. - if (RetValExp) - NRVOCandidate = getCopyElisionCandidate(FnRetType, RetValExp, CES_Strict); if (!HasDependentReturnType && !RetValExp->isTypeDependent()) { // we have a non-void function with an expression, continue checking - InitializedEntity Entity = InitializedEntity::InitializeResult(ReturnLoc, - RetType, - NRVOCandidate != nullptr); - ExprResult Res = PerformMoveOrCopyInitialization(Entity, NRVOCandidate, - RetType, RetValExp); + InitializedEntity Entity = InitializedEntity::InitializeResult( + ReturnLoc, RetType, NRVORes.isCopyElidable()); + ExprResult Res = + PerformMoveOrCopyInitialization(Entity, NRVORes, RetValExp); if (Res.isInvalid()) { // FIXME: Clean up temporaries here anyway? return StmtError(); @@ -3892,7 +3919,9 @@ return StmtError(); RetValExp = ER.get(); } - Result = ReturnStmt::Create(Context, ReturnLoc, RetValExp, NRVOCandidate); + Result = ReturnStmt::Create(Context, ReturnLoc, RetValExp, + NRVORes.isCopyElidable() ? NRVORes.Candidate + : nullptr); } // If we need to check for the named return value optimization, save the diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1052,9 +1052,18 @@ StartingScope, InstantiatingVarTemplate); if (D->isNRVOVariable()) { - QualType ReturnType = cast(DC)->getReturnType(); - if (SemaRef.isCopyElisionCandidate(ReturnType, Var, Sema::CES_Strict)) - Var->setNRVOVariable(true); + QualType ReturnType; + if (auto *F = dyn_cast(DC)) + ReturnType = F->getReturnType(); + else if (auto *F = dyn_cast(DC)) + // FIXME: get the return type here somehow... + ReturnType = SemaRef.Context.getAutoDeductType(); + else + assert(false && "Unknown context type"); + + Sema::NRVOResult Res = SemaRef.getNRVOResult(Var); + SemaRef.updNRVOResultWithRetType(Res, ReturnType); + Var->setNRVOVariable(Res.isCopyElidable()); } Var->setImplicit(D->isImplicit()); diff --git a/clang/test/CodeGen/nrvo-tracking.cpp b/clang/test/CodeGen/nrvo-tracking.cpp --- a/clang/test/CodeGen/nrvo-tracking.cpp +++ b/clang/test/CodeGen/nrvo-tracking.cpp @@ -29,8 +29,6 @@ // CHECK-LABEL: define{{.*}} void @_Z2l3v // CHECK: call {{.*}} @_ZN1XC1Ev -// CHECK-NEXT: call {{.*}} @_ZN1XC1EOS_ -// CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: ret void L(3, t, T); @@ -45,8 +43,6 @@ // CHECK-LABEL: define{{.*}} void @_Z2l5v // CHECK: call {{.*}} @_ZN1XC1Ev -// CHECK-NEXT: call {{.*}} @_ZN1XC1EOS_ -// CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: ret void L(5, t, auto); @@ -61,8 +57,6 @@ // CHECK-LABEL: define{{.*}} void @_Z2l7v // CHECK: call {{.*}} @_ZN1XC1Ev -// CHECK-NEXT: call {{.*}} @_ZN1XC1EOS_ -// CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: ret void L(7, t, decltype(auto)); @@ -113,8 +107,6 @@ // CHECK-LABEL: define{{.*}} void @_Z2f5v // CHECK: call {{.*}} @_ZN1XC1Ev -// CHECK-NEXT: call {{.*}} @_ZN1XC1EOS_ -// CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: ret void F(5, t, auto); @@ -129,8 +121,6 @@ // CHECK-LABEL: define{{.*}} void @_Z2f7v // CHECK: call {{.*}} @_ZN1XC1Ev -// CHECK-NEXT: call {{.*}} @_ZN1XC1EOS_ -// CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: ret void F(7, t, decltype(auto)); @@ -152,7 +142,11 @@ }; }()(); \ } -//B(1, X); // Uncomment this line at your own peril ;) +// CHECK-LABEL: define{{.*}} void @_Z2b1v +// CHECK: call {{.*}} @_ZN1XC1Ev +// CHECK-NEXT: call void @llvm.lifetime.end +// CHECK-NEXT: ret void +B(1, X); // CHECK-LABEL: define{{.*}} void @_Z2b2v // CHECK: call {{.*}} @_ZN1XC1Ev @@ -164,8 +158,6 @@ // CHECK-LABEL: define{{.*}} void @_Z2b3v // CHECK: call {{.*}} @_ZN1XC1Ev -// CHECK-NEXT: call {{.*}} @_ZN1XC1EOS_ -// CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: ret void B(3, T); @@ -180,8 +172,6 @@ // CHECK-LABEL: define{{.*}} void @_Z2b5v // CHECK: call {{.*}} @_ZN1XC1Ev -// CHECK-NEXT: call {{.*}} @_ZN1XC1EOS_ -// CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: call void @llvm.lifetime.end // CHECK-NEXT: ret void B(5, );