From: Richard Smith Date: Wed, 5 Jun 2013 00:46:14 +0000 (+0000) Subject: Model temporary lifetime-extension explicitly in the AST. Use this model to X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=211c8ddb5b500ed84833751363d0cfe1115f4dd3;p=clang Model temporary lifetime-extension explicitly in the AST. Use this model to handle temporaries which have been lifetime-extended to static storage duration within constant expressions. This correctly handles nested lifetime extension (through reference members of aggregates in aggregate initializers) but non-constant-expression emission hasn't yet been updated to do the same. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@183283 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/ASTContext.h b/include/clang/AST/ASTContext.h index 0982e757ea..324185f090 100644 --- a/include/clang/AST/ASTContext.h +++ b/include/clang/AST/ASTContext.h @@ -55,6 +55,7 @@ namespace clang { class ExternalASTSource; class ASTMutationListener; class IdentifierTable; + class MaterializeTemporaryExpr; class SelectorTable; class TargetInfo; class CXXABI; @@ -163,6 +164,11 @@ class ASTContext : public RefCountedBase { llvm::DenseMap ClassScopeSpecializationPattern; + /// \brief Mapping from materialized temporaries with static storage duration + /// that appear in constant initializers to their evaluated values. + llvm::DenseMap + MaterializedTemporaryValues; + /// \brief Representation of a "canonical" template template parameter that /// is used in canonical template names. class CanonicalTemplateTemplateParm : public llvm::FoldingSetNode { @@ -2117,7 +2123,12 @@ public: /// \brief Used by ParmVarDecl to retrieve on the side the /// index of the parameter when it exceeds the size of the normal bitfield. unsigned getParameterIndex(const ParmVarDecl *D) const; - + + /// \brief Get the storage for the constant value of a materialized temporary + /// of static storage duration. + APValue *getMaterializedTemporaryValue(const MaterializeTemporaryExpr *E, + bool MayCreate); + //===--------------------------------------------------------------------===// // Statistics //===--------------------------------------------------------------------===// diff --git a/include/clang/AST/Decl.h b/include/clang/AST/Decl.h index 673575af6c..a2d7b868a3 100644 --- a/include/clang/AST/Decl.h +++ b/include/clang/AST/Decl.h @@ -811,7 +811,7 @@ public: bool hasLocalStorage() const { if (getStorageClass() == SC_None) // Second check is for C++11 [dcl.stc]p4. - return !isFileVarDecl() && getTSCSpec() != TSCS_thread_local; + return !isFileVarDecl() && getTSCSpec() == TSCS_unspecified; // Return true for: Auto, Register. // Return false for: Extern, Static, PrivateExtern, OpenCLWorkGroupLocal. @@ -840,6 +840,12 @@ public: /// as static variables declared within a function. bool hasGlobalStorage() const { return !hasLocalStorage(); } + /// \brief Get the storage duration of this variable, per C++ [basid.stc]. + StorageDuration getStorageDuration() const { + return hasLocalStorage() ? SD_Automatic : + getTSCSpec() ? SD_Thread : SD_Static; + } + /// Compute the language linkage. LanguageLinkage getLanguageLinkage() const; diff --git a/include/clang/AST/ExprCXX.h b/include/clang/AST/ExprCXX.h index 42fc117fb4..9987587a0b 100644 --- a/include/clang/AST/ExprCXX.h +++ b/include/clang/AST/ExprCXX.h @@ -3824,30 +3824,60 @@ public: /// binds to the temporary. \c MaterializeTemporaryExprs are always glvalues /// (either an lvalue or an xvalue, depending on the kind of reference binding /// to it), maintaining the invariant that references always bind to glvalues. +/// +/// Reference binding and copy-elision can both extend the lifetime of a +/// temporary. When either happens, the expression will also track the +/// declaration which is responsible for the lifetime extension. class MaterializeTemporaryExpr : public Expr { +public: /// \brief The temporary-generating expression whose value will be /// materialized. Stmt *Temporary; + /// \brief The declaration which lifetime-extended this reference, if any. + /// Either a VarDecl, or (for a ctor-initializer) a FieldDecl. + const ValueDecl *ExtendingDecl; + friend class ASTStmtReader; friend class ASTStmtWriter; public: MaterializeTemporaryExpr(QualType T, Expr *Temporary, - bool BoundToLvalueReference) + bool BoundToLvalueReference, + const ValueDecl *ExtendedBy) : Expr(MaterializeTemporaryExprClass, T, BoundToLvalueReference? VK_LValue : VK_XValue, OK_Ordinary, Temporary->isTypeDependent(), Temporary->isValueDependent(), Temporary->isInstantiationDependent(), Temporary->containsUnexpandedParameterPack()), - Temporary(Temporary) { } + Temporary(Temporary), ExtendingDecl(ExtendedBy) { + } MaterializeTemporaryExpr(EmptyShell Empty) : Expr(MaterializeTemporaryExprClass, Empty) { } /// \brief Retrieve the temporary-generating subexpression whose value will /// be materialized into a glvalue. - Expr *GetTemporaryExpr() const { return reinterpret_cast(Temporary); } + Expr *GetTemporaryExpr() const { return static_cast(Temporary); } + + /// \brief Retrieve the storage duration for the materialized temporary. + StorageDuration getStorageDuration() const { + if (!ExtendingDecl) + return SD_FullExpression; + // FIXME: This is not necessarily correct for a temporary materialized + // within a default initializer. + if (isa(ExtendingDecl)) + return SD_Automatic; + return cast(ExtendingDecl)->getStorageDuration(); + } + + /// \brief Get the declaration which triggered the lifetime-extension of this + /// temporary, if any. + const ValueDecl *getExtendingDecl() const { return ExtendingDecl; } + + void setExtendingDecl(const ValueDecl *ExtendedBy) { + ExtendingDecl = ExtendedBy; + } /// \brief Determine whether this materialized temporary is bound to an /// lvalue reference; otherwise, it's bound to an rvalue reference. diff --git a/include/clang/AST/Stmt.h b/include/clang/AST/Stmt.h index b2ab672627..882cc2297e 100644 --- a/include/clang/AST/Stmt.h +++ b/include/clang/AST/Stmt.h @@ -289,7 +289,7 @@ protected: /// \brief The number of arguments to this type trait. unsigned NumArgs : 32 - 8 - 1 - NumExprBits; }; - + union { // FIXME: this is wasteful on 64-bit platforms. void *Aligner; diff --git a/include/clang/Basic/DiagnosticASTKinds.td b/include/clang/Basic/DiagnosticASTKinds.td index 06c297e550..06aca52c63 100644 --- a/include/clang/Basic/DiagnosticASTKinds.td +++ b/include/clang/Basic/DiagnosticASTKinds.td @@ -117,6 +117,10 @@ def note_constexpr_access_inactive_union_member : Note< "%select{read of|assignment to|increment of|decrement of}0 " "member %1 of union with %select{active member %3|no active member}2 " "is not allowed in a constant expression">; +def note_constexpr_access_static_temporary : Note< + "%select{read of|assignment to|increment of|decrement of}0 temporary " + "is not allowed in a constant expression outside the expression that " + "created the temporary">; def note_constexpr_modify_global : Note< "a constant expression cannot modify an object that is visible outside " "that expression">; diff --git a/include/clang/Basic/Specifiers.h b/include/clang/Basic/Specifiers.h index eb3fc659a8..7fe242ece8 100644 --- a/include/clang/Basic/Specifiers.h +++ b/include/clang/Basic/Specifiers.h @@ -212,6 +212,14 @@ namespace clang { CC_IntelOclBicc // __attribute__((intel_ocl_bicc)) }; + /// \brief The storage duration for an object (per C++ [basic.stc]). + enum StorageDuration { + SD_FullExpression, ///< Full-expression storage duration (for temporaries). + SD_Automatic, ///< Automatic storage duration (most local variables). + SD_Thread, ///< Thread storage duration. + SD_Static, ///< Static storage duration. + SD_Dynamic ///< Dynamic storage duration. + }; } // end namespace clang #endif // LLVM_CLANG_BASIC_SPECIFIERS_H diff --git a/include/clang/Sema/Initialization.h b/include/clang/Sema/Initialization.h index 727ceebe8b..244d0edb99 100644 --- a/include/clang/Sema/Initialization.h +++ b/include/clang/Sema/Initialization.h @@ -384,6 +384,13 @@ public: assert(getKind() == EK_LambdaCapture && "Not a lambda capture!"); return SourceLocation::getFromRawEncoding(Capture.Location); } + + /// Dump a representation of the initialized entity to standard error, + /// for debugging purposes. + void dump() const; + +private: + unsigned dumpImpl(raw_ostream &OS) const; }; /// \brief Describes the kind of initialization being performed, along with diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 1f89e3f1f6..44ff94e358 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -8029,6 +8029,19 @@ unsigned ASTContext::getParameterIndex(const ParmVarDecl *D) const { return I->second; } +APValue * +ASTContext::getMaterializedTemporaryValue(const MaterializeTemporaryExpr *E, + bool MayCreate) { + assert(E && E->getStorageDuration() == SD_Static && + "don't need to cache the computed value for this temporary"); + if (MayCreate) + return &MaterializedTemporaryValues[E]; + + llvm::DenseMap::iterator I = + MaterializedTemporaryValues.find(E); + return I == MaterializedTemporaryValues.end() ? 0 : &I->second; +} + bool ASTContext::AtomicUsesUnsupportedLibcall(const AtomicExpr *E) const { const llvm::Triple &T = getTargetInfo().getTriple(); if (!T.isOSDarwin()) diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 2ed36455bb..c7809e06ac 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -274,6 +274,7 @@ namespace { void VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr *Node); void VisitCXXConstructExpr(const CXXConstructExpr *Node); void VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *Node); + void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *Node); void VisitExprWithCleanups(const ExprWithCleanups *Node); void VisitUnresolvedLookupExpr(const UnresolvedLookupExpr *Node); void dumpCXXTemporary(const CXXTemporary *Temporary); @@ -1682,6 +1683,15 @@ void ASTDumper::VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *Node) { dumpCXXTemporary(Node->getTemporary()); } +void +ASTDumper::VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *Node) { + VisitExpr(Node); + if (const ValueDecl *VD = Node->getExtendingDecl()) { + OS << " extended by "; + dumpBareDeclRef(VD); + } +} + void ASTDumper::VisitExprWithCleanups(const ExprWithCleanups *Node) { VisitExpr(Node); for (unsigned i = 0, e = Node->getNumObjects(); i != e; ++i) diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index 467fb82b8d..19eaf55ba4 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -1001,6 +1001,10 @@ static bool IsGlobalLValue(APValue::LValueBase B) { const CompoundLiteralExpr *CLE = cast(E); return CLE->isFileScope() && CLE->isLValue(); } + case Expr::MaterializeTemporaryExprClass: + // A materialized temporary might have been lifetime-extended to static + // storage duration. + return cast(E)->getStorageDuration() == SD_Static; // A string literal has static storage duration. case Expr::StringLiteralClass: case Expr::PredefinedExprClass: @@ -1182,7 +1186,10 @@ const ValueDecl *GetLValueBaseDecl(const LValue &LVal) { } static bool IsLiteralLValue(const LValue &Value) { - return Value.Base.dyn_cast() && !Value.CallIndex; + if (Value.CallIndex) + return false; + const Expr *E = Value.Base.dyn_cast(); + return E && !isa(E); } static bool IsWeakLValue(const LValue &Value) { @@ -2279,11 +2286,44 @@ CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, AccessKinds AK, const Expr *Base = LVal.Base.dyn_cast(); if (!Frame) { - Info.Diag(E); - return CompleteObject(); - } + if (const MaterializeTemporaryExpr *MTE = + dyn_cast(Base)) { + assert(MTE->getStorageDuration() == SD_Static && + "should have a frame for a non-global materialized temporary"); + + // Per C++1y [expr.const]p2: + // an lvalue-to-rvalue conversion [is not allowed unless it applies to] + // - a [...] glvalue of integral or enumeration type that refers to + // a non-volatile const object [...] + // [...] + // - a [...] glvalue of literal type that refers to a non-volatile + // object whose lifetime began within the evaluation of e. + // + // C++11 misses the 'began within the evaluation of e' check and + // instead allows all temporaries, including things like: + // int &&r = 1; + // int x = ++r; + // constexpr int k = r; + // Therefore we use the C++1y rules in C++11 too. + const ValueDecl *VD = Info.EvaluatingDecl.dyn_cast(); + const ValueDecl *ED = MTE->getExtendingDecl(); + if (!(BaseType.isConstQualified() && + BaseType->isIntegralOrEnumerationType()) && + !(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) { + Info.Diag(E, diag::note_constexpr_access_static_temporary, 1) << AK; + Info.Note(MTE->getExprLoc(), diag::note_constexpr_temporary_here); + return CompleteObject(); + } - BaseVal = &Frame->Temporaries[Base]; + BaseVal = Info.Ctx.getMaterializedTemporaryValue(MTE, false); + assert(BaseVal && "got reference to unevaluated temporary"); + } else { + Info.Diag(E); + return CompleteObject(); + } + } else { + BaseVal = &Frame->Temporaries[Base]; + } // Volatile temporary objects cannot be accessed in constant expressions. if (BaseType.isVolatileQualified()) { @@ -3940,6 +3980,8 @@ public: // * Any Expr, with a CallIndex indicating the function in which the temporary // was evaluated, for cases where the MaterializeTemporaryExpr is missing // from the AST (FIXME). +// * A MaterializeTemporaryExpr that has static storage duration, with no +// CallIndex, for a lifetime-extended temporary. // plus an offset in bytes. //===----------------------------------------------------------------------===// namespace { @@ -4045,9 +4087,19 @@ bool LValueExprEvaluator::VisitMaterializeTemporaryExpr( if (!EvaluateIgnoredValue(Info, CommaLHSs[I])) return false; + // A materialized temporary with static storage duration can appear within the + // result of a constant expression evaluation, so we need to preserve its + // value for use outside this evaluation. + APValue *Value; + if (E->getStorageDuration() == SD_Static) { + Value = Info.Ctx.getMaterializedTemporaryValue(E, true); + Result.set(E); + } else { + Value = &Info.CurrentCall->Temporaries[E]; + Result.set(E, Info.CurrentCall->Index); + } + // Materialize the temporary itself. - APValue *Value = &Info.CurrentCall->Temporaries[E]; - Result.set(E, Info.CurrentCall->Index); if (!EvaluateInPlace(*Value, Info, Result, Inner)) return false; @@ -7608,10 +7660,10 @@ void Expr::EvaluateForOverflow(const ASTContext &Ctx, } } - bool Expr::EvalResult::isGlobalLValue() const { - assert(Val.isLValue()); - return IsGlobalLValue(Val.getLValueBase()); - } +bool Expr::EvalResult::isGlobalLValue() const { + assert(Val.isLValue()); + return IsGlobalLValue(Val.getLValueBase()); +} /// isIntegerConstantExpr - this recursive routine will test if an expression is diff --git a/lib/CodeGen/CGExprConstant.cpp b/lib/CodeGen/CGExprConstant.cpp index f5c8187c26..ca331cc92b 100644 --- a/lib/CodeGen/CGExprConstant.cpp +++ b/lib/CodeGen/CGExprConstant.cpp @@ -1003,6 +1003,15 @@ public: case Expr::CXXUuidofExprClass: { return CGM.GetAddrOfUuidDescriptor(cast(E)); } + case Expr::MaterializeTemporaryExprClass: { + MaterializeTemporaryExpr *MTE = cast(E); + assert(MTE->getStorageDuration() == SD_Static); + SmallVector CommaLHSs; + SmallVector Adjustments; + const Expr *Inner = MTE->GetTemporaryExpr() + ->skipRValueSubobjectAdjustments(CommaLHSs, Adjustments); + return CGM.GetAddrOfGlobalTemporary(MTE, Inner); + } } return 0; diff --git a/lib/CodeGen/CodeGenModule.cpp b/lib/CodeGen/CodeGenModule.cpp index 3675e849aa..4d64624dbc 100644 --- a/lib/CodeGen/CodeGenModule.cpp +++ b/lib/CodeGen/CodeGenModule.cpp @@ -2735,6 +2735,63 @@ llvm::Constant *CodeGenModule::GetAddrOfConstantCString(const std::string &Str, return GetAddrOfConstantString(StrWithNull, GlobalName, Alignment); } +llvm::Constant *CodeGenModule::GetAddrOfGlobalTemporary( + const MaterializeTemporaryExpr *E, const Expr *Inner) { + assert((E->getStorageDuration() == SD_Static || + E->getStorageDuration() == SD_Thread) && "not a global temporary"); + const VarDecl *VD = cast(E->getExtendingDecl()); + + // If we're not materializing a subobject of the temporary, keep the + // cv-qualifiers from the type of the MaterializeTemporaryExpr. + if (Inner == E->GetTemporaryExpr()) + Inner = E; + + llvm::Constant *&Slot = MaterializedGlobalTemporaryMap[E]; + if (Slot) + return Slot; + + // FIXME: If an externally-visible declaration extends multiple temporaries, + // we need to give each temporary the same name in every translation unit (and + // we also need to make the temporaries externally-visible). + SmallString<256> Name; + llvm::raw_svector_ostream Out(Name); + getCXXABI().getMangleContext().mangleReferenceTemporary(VD, Out); + Out.flush(); + + llvm::Constant *InitialValue = 0; + APValue *Value = 0; + if (E->getStorageDuration() == SD_Static) { + // We might have a constant initializer for this temporary. + Value = getContext().getMaterializedTemporaryValue(E, false); + if (Value && Value->isUninit()) + Value = 0; + } + + bool Constant; + if (Value) { + // The temporary has a constant initializer, use it. + InitialValue = EmitConstantValue(*Value, Inner->getType(), 0); + Constant = isTypeConstant(Inner->getType(), /*ExcludeCtor*/Value); + } else { + // No constant initializer, the initialization will be provided when we + // initialize the declaration which performed lifetime extension. + InitialValue = EmitNullConstant(Inner->getType()); + Constant = false; + } + + // Create a global variable for this lifetime-extended temporary. + llvm::GlobalVariable *GV = + new llvm::GlobalVariable(getModule(), InitialValue->getType(), Constant, + llvm::GlobalValue::PrivateLinkage, InitialValue, + Name.c_str()); + GV->setAlignment( + getContext().getTypeAlignInChars(Inner->getType()).getQuantity()); + if (VD->getTLSKind()) + setTLSMode(GV, *VD); + Slot = GV; + return GV; +} + /// EmitObjCPropertyImplementations - Emit information for synthesized /// properties for an implementation. void CodeGenModule::EmitObjCPropertyImplementations(const diff --git a/lib/CodeGen/CodeGenModule.h b/lib/CodeGen/CodeGenModule.h index 2777f95922..aa48cce08b 100644 --- a/lib/CodeGen/CodeGenModule.h +++ b/lib/CodeGen/CodeGenModule.h @@ -307,7 +307,8 @@ class CodeGenModule : public CodeGenTypeCache { llvm::StringMap ConstantStringMap; llvm::DenseMap StaticLocalDeclMap; llvm::DenseMap StaticLocalDeclGuardMap; - + llvm::DenseMap MaterializedGlobalTemporaryMap; + llvm::DenseMap AtomicSetterHelperFnMap; llvm::DenseMap AtomicGetterHelperFnMap; @@ -731,7 +732,12 @@ public: /// GetAddrOfConstantCompoundLiteral - Returns a pointer to a constant global /// variable for the given file-scope compound literal expression. llvm::Constant *GetAddrOfConstantCompoundLiteral(const CompoundLiteralExpr*E); - + + /// \brief Returns a pointer to a global variable representing a temporary + /// with static or thread storage duration. + llvm::Constant *GetAddrOfGlobalTemporary(const MaterializeTemporaryExpr *E, + const Expr *Inner); + /// \brief Retrieve the record type that describes the state of an /// Objective-C fast enumeration loop (for..in). QualType getObjCFastEnumerationStateType(); diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index a70e391721..2a3482238d 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -8353,7 +8353,7 @@ static QualType CheckAddressOfOperand(Sema &S, ExprResult &OrigOp, return QualType(); // Materialize the temporary as an lvalue so that we can take its address. OrigOp = op = new (S.Context) - MaterializeTemporaryExpr(op->getType(), OrigOp.take(), true); + MaterializeTemporaryExpr(op->getType(), OrigOp.take(), true, 0); } else if (isa(op)) { return S.Context.getPointerType(op->getType()); } else if (lval == Expr::LV_MemberFunction) { diff --git a/lib/Sema/SemaInit.cpp b/lib/Sema/SemaInit.cpp index 2e11bc98ac..ab400cd11b 100644 --- a/lib/Sema/SemaInit.cpp +++ b/lib/Sema/SemaInit.cpp @@ -2502,6 +2502,46 @@ bool InitializedEntity::allowsNRVO() const { return false; } +unsigned InitializedEntity::dumpImpl(raw_ostream &OS) const { + unsigned Depth = getParent() ? getParent()->dumpImpl(OS) : 0; + for (unsigned I = 0; I != Depth; ++I) + OS << "`-"; + + switch (getKind()) { + case EK_Variable: OS << "Variable"; break; + case EK_Parameter: OS << "Parameter"; break; + case EK_Result: OS << "Result"; break; + case EK_Exception: OS << "Exception"; break; + case EK_Member: OS << "Member"; break; + case EK_New: OS << "New"; break; + case EK_Temporary: OS << "Temporary"; break; + case EK_CompoundLiteralInit: OS << "CompoundLiteral";break; + case EK_Base: OS << "Base"; break; + case EK_Delegating: OS << "Delegating"; break; + case EK_ArrayElement: OS << "ArrayElement " << Index; break; + case EK_VectorElement: OS << "VectorElement " << Index; break; + case EK_ComplexElement: OS << "ComplexElement " << Index; break; + case EK_BlockElement: OS << "Block"; break; + case EK_LambdaCapture: + OS << "LambdaCapture "; + getCapturedVar()->printName(OS); + break; + } + + if (Decl *D = getDecl()) { + OS << " "; + cast(D)->printQualifiedName(OS); + } + + OS << " '" << getType().getAsString() << "'\n"; + + return Depth + 1; +} + +void InitializedEntity::dump() const { + dumpImpl(llvm::errs()); +} + //===----------------------------------------------------------------------===// // Initialization sequence //===----------------------------------------------------------------------===// @@ -5089,6 +5129,143 @@ InitializedEntityOutlivesFullExpression(const InitializedEntity &Entity) { llvm_unreachable("unknown entity kind"); } +/// Determine the declaration which an initialized entity ultimately refers to, +/// for the purpose of lifetime-extending a temporary bound to a reference in +/// the initialization of \p Entity. +static const ValueDecl * +getDeclForTemporaryLifetimeExtension(const InitializedEntity &Entity, + const ValueDecl *FallbackDecl = 0) { + // C++11 [class.temporary]p5: + switch (Entity.getKind()) { + case InitializedEntity::EK_Variable: + // The temporary [...] persists for the lifetime of the reference + return Entity.getDecl(); + + case InitializedEntity::EK_Member: + // For subobjects, we look at the complete object. + if (Entity.getParent()) + return getDeclForTemporaryLifetimeExtension(*Entity.getParent(), + Entity.getDecl()); + + // except: + // -- A temporary bound to a reference member in a constructor's + // ctor-initializer persists until the constructor exits. + return Entity.getDecl(); + + case InitializedEntity::EK_Parameter: + // -- A temporary bound to a reference parameter in a function call + // persists until the completion of the full-expression containing + // the call. + case InitializedEntity::EK_Result: + // -- The lifetime of a temporary bound to the returned value in a + // function return statement is not extended; the temporary is + // destroyed at the end of the full-expression in the return statement. + case InitializedEntity::EK_New: + // -- A temporary bound to a reference in a new-initializer persists + // until the completion of the full-expression containing the + // new-initializer. + return 0; + + case InitializedEntity::EK_Temporary: + case InitializedEntity::EK_CompoundLiteralInit: + // We don't yet know the storage duration of the surrounding temporary. + // Assume it's got full-expression duration for now, it will patch up our + // storage duration if that's not correct. + return 0; + + case InitializedEntity::EK_ArrayElement: + // For subobjects, we look at the complete object. + return getDeclForTemporaryLifetimeExtension(*Entity.getParent(), + FallbackDecl); + + case InitializedEntity::EK_Base: + case InitializedEntity::EK_Delegating: + // We can reach this case for aggregate initialization in a constructor: + // struct A { int &&r; }; + // struct B : A { B() : A{0} {} }; + // In this case, use the innermost field decl as the context. + return FallbackDecl; + + case InitializedEntity::EK_BlockElement: + case InitializedEntity::EK_LambdaCapture: + case InitializedEntity::EK_Exception: + case InitializedEntity::EK_VectorElement: + case InitializedEntity::EK_ComplexElement: + llvm_unreachable("should not materialize a temporary to initialize this"); + } +} + +static void performLifetimeExtension(Expr *Init, const ValueDecl *ExtendingD); + +/// Update a glvalue expression that is used as the initializer of a reference +/// to note that its lifetime is extended. +static void performReferenceExtension(Expr *Init, const ValueDecl *ExtendingD) { + if (InitListExpr *ILE = dyn_cast(Init)) { + if (ILE->getNumInits() == 1 && ILE->isGLValue()) { + // This is just redundant braces around an initializer. Step over it. + Init = ILE->getInit(0); + } + } + + if (MaterializeTemporaryExpr *ME = dyn_cast(Init)) { + // Update the storage duration of the materialized temporary. + // FIXME: Rebuild the expression instead of mutating it. + ME->setExtendingDecl(ExtendingD); + performLifetimeExtension(ME->GetTemporaryExpr(), ExtendingD); + } +} + +/// Update a prvalue expression that is going to be materialized as a +/// lifetime-extended temporary. +static void performLifetimeExtension(Expr *Init, const ValueDecl *ExtendingD) { + // Dig out the expression which constructs the extended temporary. + SmallVector CommaLHSs; + SmallVector Adjustments; + Init = const_cast( + Init->skipRValueSubobjectAdjustments(CommaLHSs, Adjustments)); + + if (InitListExpr *ILE = dyn_cast(Init)) { + if (ILE->initializesStdInitializerList()) { + // FIXME: If this is an InitListExpr which creates a std::initializer_list + // object, we also need to lifetime-extend the underlying array + // itself. Fix the representation to explicitly materialize an + // array temporary so we can model this properly. + for (unsigned I = 0, N = ILE->getNumInits(); I != N; ++I) + performLifetimeExtension(ILE->getInit(I), ExtendingD); + return; + } + + CXXRecordDecl *RD = Init->getType()->getAsCXXRecordDecl(); + if (RD) { + assert(RD->isAggregate() && "aggregate init on non-aggregate"); + + // If we lifetime-extend a braced initializer which is initializing an + // aggregate, and that aggregate contains reference members which are + // bound to temporaries, those temporaries are also lifetime-extended. + if (RD->isUnion() && ILE->getInitializedFieldInUnion() && + ILE->getInitializedFieldInUnion()->getType()->isReferenceType()) + performReferenceExtension(ILE->getInit(0), ExtendingD); + else { + unsigned Index = 0; + for (RecordDecl::field_iterator I = RD->field_begin(), + E = RD->field_end(); + I != E; ++I) { + if (I->isUnnamedBitfield()) + continue; + if (I->getType()->isReferenceType()) + performReferenceExtension(ILE->getInit(Index), ExtendingD); + else if (isa(ILE->getInit(Index))) + // This may be either aggregate-initialization of a member or + // initialization of a std::initializer_list object. Either way, + // we should recursively lifetime-extend that initializer. + performLifetimeExtension(ILE->getInit(Index), ExtendingD); + ++Index; + } + } + } + } +} + ExprResult InitializationSequence::Perform(Sema &S, const InitializedEntity &Entity, @@ -5326,7 +5503,7 @@ InitializationSequence::Perform(Sema &S, break; - case SK_BindReferenceToTemporary: + case SK_BindReferenceToTemporary: { // Make sure the "temporary" is actually an rvalue. assert(CurInit.get()->isRValue() && "not a temporary"); @@ -5334,11 +5511,17 @@ InitializationSequence::Perform(Sema &S, if (S.CheckExceptionSpecCompatibility(CurInit.get(), DestType)) return ExprError(); + // Maybe lifetime-extend the temporary's subobjects to match the + // entity's lifetime. + const ValueDecl *ExtendingDecl = + getDeclForTemporaryLifetimeExtension(Entity); + if (ExtendingDecl) + performLifetimeExtension(CurInit.get(), ExtendingDecl); + // Materialize the temporary into memory. CurInit = new (S.Context) MaterializeTemporaryExpr( - Entity.getType().getNonReferenceType(), - CurInit.get(), - Entity.getType()->isLValueReferenceType()); + Entity.getType().getNonReferenceType(), CurInit.get(), + Entity.getType()->isLValueReferenceType(), ExtendingDecl); // If we're binding to an Objective-C object that has lifetime, we // need cleanups. @@ -5347,6 +5530,7 @@ InitializationSequence::Perform(Sema &S, S.ExprNeedsCleanups = true; break; + } case SK_ExtraneousCopyToTemporary: CurInit = CopyObject(S, Step->Type, Entity, CurInit, diff --git a/lib/Serialization/ASTReaderStmt.cpp b/lib/Serialization/ASTReaderStmt.cpp index 04fc2bde2e..9e0157d7af 100644 --- a/lib/Serialization/ASTReaderStmt.cpp +++ b/lib/Serialization/ASTReaderStmt.cpp @@ -1576,6 +1576,7 @@ void ASTStmtReader::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) { void ASTStmtReader::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) { VisitExpr(E); E->Temporary = Reader.ReadSubExpr(); + E->ExtendingDecl = ReadDeclAs(Record, Idx); } void ASTStmtReader::VisitOpaqueValueExpr(OpaqueValueExpr *E) { diff --git a/lib/Serialization/ASTWriterStmt.cpp b/lib/Serialization/ASTWriterStmt.cpp index 5f7ac01bae..5a8baa9d88 100644 --- a/lib/Serialization/ASTWriterStmt.cpp +++ b/lib/Serialization/ASTWriterStmt.cpp @@ -1572,6 +1572,7 @@ void ASTStmtWriter::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) { void ASTStmtWriter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) { VisitExpr(E); Writer.AddStmt(E->Temporary); + Writer.AddDeclRef(E->ExtendingDecl, Record); Code = serialization::EXPR_MATERIALIZE_TEMPORARY; } diff --git a/test/CodeGenCXX/const-init-cxx11.cpp b/test/CodeGenCXX/const-init-cxx11.cpp index 9e5b5ef091..46eca0e5c9 100644 --- a/test/CodeGenCXX/const-init-cxx11.cpp +++ b/test/CodeGenCXX/const-init-cxx11.cpp @@ -224,12 +224,50 @@ namespace LiteralReference { constexpr Lit() : n(5) {} int n; }; - // FIXME: This should have static initialization, but we do not implement - // that yet. For now, just check that we don't set the (pointer) value of - // the reference to 5! - // - // CHECK: @_ZN16LiteralReference3litE = global {{.*}} null + + // This creates a non-const temporary and binds a reference to it. + // CHECK: @[[TEMP:.*]] = private global {{.*}} { i32 5 }, align 4 + // CHECK: @_ZN16LiteralReference3litE = constant {{.*}} @[[TEMP]], align 8 const Lit &lit = Lit(); + + // This creates a const temporary as part of the reference initialization. + // CHECK: @[[TEMP:.*]] = private constant {{.*}} { i32 5 }, align 4 + // CHECK: @_ZN16LiteralReference4lit2E = constant {{.*}} @[[TEMP]], align 8 + const Lit &lit2 = {}; + + struct A { int &&r1; const int &&r2; }; + struct B { A &&a1; const A &&a2; }; + B b = { { 0, 1 }, { 2, 3 } }; + // CHECK: @[[TEMP0:.*]] = private global i32 0, align 4 + // CHECK: @[[TEMP1:.*]] = private constant i32 1, align 4 + // CHECK: @[[TEMPA1:.*]] = private global {{.*}} { i32* @[[TEMP0]], i32* @[[TEMP1]] }, align 8 + // CHECK: @[[TEMP2:.*]] = private global i32 2, align 4 + // CHECK: @[[TEMP3:.*]] = private constant i32 3, align 4 + // CHECK: @[[TEMPA2:.*]] = private constant {{.*}} { i32* @[[TEMP2]], i32* @[[TEMP3]] }, align 8 + // CHECK: @_ZN16LiteralReference1bE = global {{.*}} { {{.*}}* @[[TEMPA1]], {{.*}}* @[[TEMPA2]] }, align 8 + + struct Subobj { + int a, b, c; + }; + // CHECK: @[[TEMP:.*]] = private global {{.*}} { i32 1, i32 2, i32 3 }, align 4 + // CHECK: @_ZN16LiteralReference2soE = constant {{.*}} (i8* getelementptr {{.*}} @[[TEMP]]{{.*}}, i64 4) + constexpr int &&so = Subobj{ 1, 2, 3 }.b; + + struct Dummy { int padding; }; + struct Derived : Dummy, Subobj { + constexpr Derived() : Dummy{200}, Subobj{4, 5, 6} {} + }; + using ConstDerived = const Derived; + // CHECK: @[[TEMPCOMMA:.*]] = private constant {{.*}} { i32 200, i32 4, i32 5, i32 6 } + // CHECK: @_ZN16LiteralReference5commaE = constant {{.*}} getelementptr {{.*}} @[[TEMPCOMMA]]{{.*}}, i64 8) + constexpr const int &comma = (1, (2, ConstDerived{}).b); + + // CHECK: @[[TEMPDERIVED:.*]] = private global {{.*}} { i32 200, i32 4, i32 5, i32 6 } + // CHECK: @_ZN16LiteralReference4baseE = constant {{.*}} getelementptr {{.*}} @[[TEMPDERIVED]]{{.*}}, i64 4) + constexpr Subobj &&base = Derived{}; + + // CHECK: @_ZN16LiteralReference7derivedE = constant {{.*}} @[[TEMPDERIVED]] + constexpr Derived &derived = static_cast(base); } namespace NonLiteralConstexpr { @@ -330,6 +368,17 @@ namespace PR13273 { extern const S s {}; } +namespace UnemittedTemporaryDecl { + constexpr int &&ref = 0; + extern constexpr int &ref2 = ref; + // CHECK: @_ZGRN22UnemittedTemporaryDecl3refE = private global i32 0 + + // FIXME: This declaration should not be emitted -- it isn't odr-used. + // CHECK: @_ZN22UnemittedTemporaryDecl3refE + + // CHECK: @_ZN22UnemittedTemporaryDecl4ref2E = constant i32* @_ZGRN22UnemittedTemporaryDecl3refE +} + // CHECK: @_ZZN12LocalVarInit3aggEvE1a = internal constant {{.*}} i32 101 // CHECK: @_ZZN12LocalVarInit4ctorEvE1a = internal constant {{.*}} i32 102 // CHECK: @_ZZN12LocalVarInit8mutable_EvE1a = private unnamed_addr constant {{.*}} i32 103 diff --git a/test/CodeGenCXX/const-init-cxx1y.cpp b/test/CodeGenCXX/const-init-cxx1y.cpp index 02293c86dd..b17337b454 100644 --- a/test/CodeGenCXX/const-init-cxx1y.cpp +++ b/test/CodeGenCXX/const-init-cxx1y.cpp @@ -17,4 +17,13 @@ B b; // CHECK: @b = global {{.*}} i32 1, {{.*}} { i32 2 }, {{.*}} { i32 3 }, {{.*}} { i32 4 } // CHECK-NOT: _ZN1BC -// CHECK: __cxa_atexit + +namespace ModifyStaticTemporary { + struct A { int &&temporary; int x; }; + constexpr int f(int &r) { r *= 9; return r - 12; } + A a = { 6, f(a.temporary) }; + // CHECK: @_ZGRN21ModifyStaticTemporary1aE = private global i32 54 + // CHECK: @_ZN21ModifyStaticTemporary1aE = global {{.*}} i32* @_ZGRN21ModifyStaticTemporary1aE, i32 42 +} + +// CHECK: __cxa_atexit({{.*}} @_ZN1BD1Ev {{.*}} @b diff --git a/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp b/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp index 75c04e7fae..54d88f565e 100644 --- a/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp +++ b/test/CodeGenCXX/cxx0x-initializer-stdinitializerlist.cpp @@ -284,4 +284,7 @@ namespace dtors { void f() { std::initializer_list{ S(), S() }; } + void g() { + auto x = std::initializer_list{ S(), S() }; + } } diff --git a/test/SemaCXX/constant-expression-cxx11.cpp b/test/SemaCXX/constant-expression-cxx11.cpp index f7bd01c59c..3a460c93af 100644 --- a/test/SemaCXX/constant-expression-cxx11.cpp +++ b/test/SemaCXX/constant-expression-cxx11.cpp @@ -339,6 +339,30 @@ static_assert(!same(4, 4), ""); static_assert(same(n, n), ""); static_assert(sameTemporary(9), ""); +struct A { int &&r; }; +struct B { A &&a1; A &&a2; }; + +constexpr B b1 { { 1 }, { 2 } }; // expected-note {{temporary created here}} +static_assert(&b1.a1 != &b1.a2, ""); +static_assert(&b1.a1.r != &b1.a2.r, ""); // expected-error {{constant expression}} expected-note {{outside the expression that created the temporary}} + +constexpr B &&b2 { { 3 }, { 4 } }; // expected-note {{temporary created here}} +static_assert(&b1 != &b2, ""); +static_assert(&b1.a1 != &b2.a1, ""); // expected-error {{constant expression}} expected-note {{outside the expression that created the temporary}} + +constexpr thread_local B b3 { { 1 }, { 2 } }; // expected-error {{constant expression}} expected-note {{reference to temporary}} expected-note {{here}} +void foo() { + constexpr static B b1 { { 1 }, { 2 } }; // ok + constexpr thread_local B b2 { { 1 }, { 2 } }; // expected-error {{constant expression}} expected-note {{reference to temporary}} expected-note {{here}} + constexpr B b3 { { 1 }, { 2 } }; // expected-error {{constant expression}} expected-note {{reference to temporary}} expected-note {{here}} +} + +constexpr B &&b4 = ((1, 2), 3, 4, B { {10}, {{20}} }); // expected-warning 4{{unused}} +static_assert(&b4 != &b2, ""); + +// Proposed DR: copy-elision doesn't trigger lifetime extension. +constexpr B b5 = B{ {0}, {0} }; // expected-error {{constant expression}} expected-note {{reference to temporary}} expected-note {{here}} + } constexpr int strcmp_ce(const char *p, const char *q) { @@ -572,7 +596,7 @@ struct E { constexpr E() : p(&p) {} void *p; }; -constexpr const E &e1 = E(); // expected-error {{constant expression}} expected-note {{reference to temporary is not a constant expression}} expected-note {{temporary created here}} +constexpr const E &e1 = E(); // This is a constant expression if we elide the copy constructor call, and // is not a constant expression if we don't! But we do, so it is. constexpr E e2 = E(); @@ -1282,8 +1306,23 @@ struct Wrap { constexpr const Wrap &g(const Wrap &w) { return w; } constexpr int k2 = g({0}).value; // ok -constexpr const int &i = 0; // expected-error {{constant expression}} expected-note {{temporary}} expected-note 2{{here}} -constexpr const int j = i; // expected-error {{constant expression}} expected-note {{initializer of 'i' is not a constant expression}} +// The temporary here has static storage duration, so we can bind a constexpr +// reference to it. +constexpr const int &i = 1; +constexpr const int j = i; +static_assert(j == 1, ""); + +// The temporary here is not const, so it can't be read outside the expression +// in which it was created (per the C++14 rules, which we use to avoid a C++11 +// defect). +constexpr int &&k = 1; // expected-note {{temporary created here}} +constexpr const int l = k; // expected-error {{constant expression}} expected-note {{read of temporary}} + +void f() { + // The temporary here has automatic storage duration, so we can't bind a + // constexpr reference to it. + constexpr const int &i = 1; // expected-error {{constant expression}} expected-note 2{{temporary}} +} } diff --git a/test/SemaCXX/constant-expression-cxx1y.cpp b/test/SemaCXX/constant-expression-cxx1y.cpp index 23b5d0b460..40a98d4ca9 100644 --- a/test/SemaCXX/constant-expression-cxx1y.cpp +++ b/test/SemaCXX/constant-expression-cxx1y.cpp @@ -715,3 +715,13 @@ namespace deduced_return_type { static_assert(f() == 0, ""); static_assert(g(true), ""); } + +namespace modify_temporary_during_construction { + struct A { int &&temporary; int x; int y; }; + constexpr int f(int &r) { r *= 9; return r - 12; } + // FIXME: The 'uninitialized' warning here is bogus. + constexpr A a = { 6, f(a.temporary), a.temporary }; // expected-warning {{uninitialized}} expected-note {{temporary created here}} + static_assert(a.x == 42, ""); + static_assert(a.y == 54, ""); + constexpr int k = a.temporary++; // expected-error {{constant expression}} expected-note {{outside the expression that created the temporary}} +}