summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--clang/include/clang/AST/Decl.h28
-rw-r--r--clang/include/clang/Basic/DiagnosticASTKinds.td6
-rw-r--r--clang/include/clang/Basic/DiagnosticSemaKinds.td2
-rw-r--r--clang/lib/AST/ASTContext.cpp2
-rw-r--r--clang/lib/AST/Decl.cpp12
-rw-r--r--clang/lib/AST/ExprConstant.cpp136
-rw-r--r--clang/lib/AST/Interp/Interp.cpp2
-rw-r--r--clang/lib/AST/TextNodeDumper.cpp2
-rw-r--r--clang/lib/CodeGen/CGCall.cpp2
-rw-r--r--clang/lib/CodeGen/CGClass.cpp2
-rw-r--r--clang/lib/CodeGen/CGDecl.cpp15
-rw-r--r--clang/lib/CodeGen/CGDeclCXX.cpp11
-rw-r--r--clang/lib/CodeGen/CodeGenModule.cpp4
-rw-r--r--clang/lib/CodeGen/ItaniumCXXABI.cpp2
-rw-r--r--clang/lib/Sema/SemaDeclCXX.cpp13
-rw-r--r--clang/lib/Serialization/ASTReaderDecl.cpp5
-rw-r--r--clang/lib/Serialization/ASTWriterDecl.cpp11
-rw-r--r--clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp17
-rw-r--r--clang/test/CXX/expr/expr.const/p6-2a.cpp43
-rw-r--r--clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp1
-rw-r--r--clang/test/CodeGenCXX/const-init-cxx2a.cpp57
-rw-r--r--clang/test/CodeGenCXX/no_destroy.cpp8
-rw-r--r--clang/test/CodeGenCXX/non-const-init-cxx2a.cpp19
23 files changed, 325 insertions, 75 deletions
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 6b6a8abc3ab..cbac50daf14 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -808,12 +808,19 @@ struct EvaluatedStmt {
/// valid if CheckedICE is true.
bool IsICE : 1;
+ /// Whether this variable is known to have constant destruction. That is,
+ /// whether running the destructor on the initial value is a side-effect
+ /// (and doesn't inspect any state that might have changed during program
+ /// execution). This is currently only computed if the destructor is
+ /// non-trivial.
+ bool HasConstantDestruction : 1;
+
Stmt *Value;
APValue Evaluated;
- EvaluatedStmt() : WasEvaluated(false), IsEvaluating(false), CheckedICE(false),
- CheckingICE(false), IsICE(false) {}
-
+ EvaluatedStmt()
+ : WasEvaluated(false), IsEvaluating(false), CheckedICE(false),
+ CheckingICE(false), IsICE(false), HasConstantDestruction(false) {}
};
/// Represents a variable declaration or definition.
@@ -1267,6 +1274,14 @@ public:
/// to untyped APValue if the value could not be evaluated.
APValue *getEvaluatedValue() const;
+ /// Evaluate the destruction of this variable to determine if it constitutes
+ /// constant destruction.
+ ///
+ /// \pre isInitICE()
+ /// \return \c true if this variable has constant destruction, \c false if
+ /// not.
+ bool evaluateDestruction(SmallVectorImpl<PartialDiagnosticAt> &Notes) const;
+
/// Determines whether it is already known whether the
/// initializer is an integral constant expression or not.
bool isInitKnownICE() const;
@@ -1505,9 +1520,14 @@ public:
// has no definition within this source file.
bool isKnownToBeDefined() const;
- /// Do we need to emit an exit-time destructor for this variable?
+ /// Is destruction of this variable entirely suppressed? If so, the variable
+ /// need not have a usable destructor at all.
bool isNoDestroy(const ASTContext &) const;
+ /// Do we need to emit an exit-time destructor for this variable, and if so,
+ /// what kind?
+ QualType::DestructionKind needsDestruction(const ASTContext &Ctx) const;
+
// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
static bool classofKind(Kind K) { return K >= firstVar && K <= lastVar; }
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 7f935d4fa6e..eb2a1f02fd8 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -145,8 +145,10 @@ def note_constexpr_access_volatile_obj : Note<
"a constant expression">;
def note_constexpr_volatile_here : Note<
"volatile %select{temporary created|object declared|member declared}0 here">;
-def note_constexpr_ltor_mutable : Note<
- "read of mutable member %0 is not allowed in a constant expression">;
+def note_constexpr_access_mutable : Note<
+ "%select{read of|read of|assignment to|increment of|decrement of|"
+ "member call on|dynamic_cast of|typeid applied to|destruction of}0 "
+ "mutable member %1 is not allowed in a constant expression">;
def note_constexpr_ltor_non_const_int : Note<
"read of non-const variable %0 is not allowed in a constant expression">;
def note_constexpr_ltor_non_constexpr : Note<
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index adc658bb294..d1b9aea0294 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2384,6 +2384,8 @@ def err_constexpr_var_non_literal : Error<
"constexpr variable cannot have non-literal type %0">;
def err_constexpr_var_requires_const_init : Error<
"constexpr variable %0 must be initialized by a constant expression">;
+def err_constexpr_var_requires_const_destruction : Error<
+ "constexpr variable %0 must have constant destruction">;
def err_constexpr_redecl_mismatch : Error<
"%select{non-constexpr|constexpr|consteval}1 declaration of %0"
" follows %select{non-constexpr|constexpr|consteval}2 declaration">;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 091b2fe3462..f6919938d5a 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -10064,7 +10064,7 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) {
return false;
// Variables that have destruction with side-effects are required.
- if (VD->getType().isDestructedType())
+ if (VD->needsDestruction(*this))
return true;
// Variables that have initialization with side-effects are required.
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 19a012b332d..9ebf1c32629 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2592,6 +2592,18 @@ bool VarDecl::isNoDestroy(const ASTContext &Ctx) const {
!hasAttr<AlwaysDestroyAttr>()));
}
+QualType::DestructionKind
+VarDecl::needsDestruction(const ASTContext &Ctx) const {
+ if (EvaluatedStmt *Eval = Init.dyn_cast<EvaluatedStmt *>())
+ if (Eval->HasConstantDestruction)
+ return QualType::DK_none;
+
+ if (isNoDestroy(Ctx))
+ return QualType::DK_none;
+
+ return getType().isDestructedType();
+}
+
MemberSpecializationInfo *VarDecl::getMemberSpecializationInfo() const {
if (isStaticDataMember())
// FIXME: Remove ?
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 1b3ace087e4..33608db6e42 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -744,6 +744,15 @@ namespace {
/// evaluated, if any.
APValue::LValueBase EvaluatingDecl;
+ enum class EvaluatingDeclKind {
+ None,
+ /// We're evaluating the construction of EvaluatingDecl.
+ Ctor,
+ /// We're evaluating the destruction of EvaluatingDecl.
+ Dtor,
+ };
+ EvaluatingDeclKind IsEvaluatingDecl = EvaluatingDeclKind::None;
+
/// EvaluatingDeclValue - This is the value being constructed for the
/// declaration whose initializer is being evaluated, if any.
APValue *EvaluatingDeclValue;
@@ -902,8 +911,10 @@ namespace {
discardCleanups();
}
- void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value) {
+ void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value,
+ EvaluatingDeclKind EDK = EvaluatingDeclKind::Ctor) {
EvaluatingDecl = Base;
+ IsEvaluatingDecl = EDK;
EvaluatingDeclValue = &Value;
}
@@ -2913,8 +2924,8 @@ static bool isReadByLvalueToRvalueConversion(QualType T) {
/// Diagnose an attempt to read from any unreadable field within the specified
/// type, which might be a class type.
-static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E,
- QualType T) {
+static bool diagnoseMutableFields(EvalInfo &Info, const Expr *E, AccessKinds AK,
+ QualType T) {
CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
if (!RD)
return false;
@@ -2929,17 +2940,17 @@ static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E,
// FIXME: Add core issue number for the union case.
if (Field->isMutable() &&
(RD->isUnion() || isReadByLvalueToRvalueConversion(Field->getType()))) {
- Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1) << Field;
+ Info.FFDiag(E, diag::note_constexpr_access_mutable, 1) << AK << Field;
Info.Note(Field->getLocation(), diag::note_declared_at);
return true;
}
- if (diagnoseUnreadableFields(Info, E, Field->getType()))
+ if (diagnoseMutableFields(Info, E, AK, Field->getType()))
return true;
}
for (auto &BaseSpec : RD->bases())
- if (diagnoseUnreadableFields(Info, E, BaseSpec.getType()))
+ if (diagnoseMutableFields(Info, E, AK, BaseSpec.getType()))
return true;
// All mutable fields were empty, and thus not actually read.
@@ -2947,7 +2958,8 @@ static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E,
}
static bool lifetimeStartedInEvaluation(EvalInfo &Info,
- APValue::LValueBase Base) {
+ APValue::LValueBase Base,
+ bool MutableSubobject = false) {
// A temporary we created.
if (Base.getCallIndex())
return true;
@@ -2956,19 +2968,42 @@ static bool lifetimeStartedInEvaluation(EvalInfo &Info,
if (!Evaluating)
return false;
- // The variable whose initializer we're evaluating.
- if (auto *BaseD = Base.dyn_cast<const ValueDecl*>())
- if (declaresSameEntity(Evaluating, BaseD))
- return true;
+ auto *BaseD = Base.dyn_cast<const ValueDecl*>();
- // A temporary lifetime-extended by the variable whose initializer we're
- // evaluating.
- if (auto *BaseE = Base.dyn_cast<const Expr *>())
- if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE))
- if (declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating))
- return true;
+ switch (Info.IsEvaluatingDecl) {
+ case EvalInfo::EvaluatingDeclKind::None:
+ return false;
- return false;
+ case EvalInfo::EvaluatingDeclKind::Ctor:
+ // The variable whose initializer we're evaluating.
+ if (BaseD)
+ return declaresSameEntity(Evaluating, BaseD);
+
+ // A temporary lifetime-extended by the variable whose initializer we're
+ // evaluating.
+ if (auto *BaseE = Base.dyn_cast<const Expr *>())
+ if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE))
+ return declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating);
+ return false;
+
+ case EvalInfo::EvaluatingDeclKind::Dtor:
+ // C++2a [expr.const]p6:
+ // [during constant destruction] the lifetime of a and its non-mutable
+ // subobjects (but not its mutable subobjects) [are] considered to start
+ // within e.
+ //
+ // FIXME: We can meaningfully extend this to cover non-const objects, but
+ // we will need special handling: we should be able to access only
+ // subobjects of such objects that are themselves declared const.
+ if (!BaseD ||
+ !(BaseD->getType().isConstQualified() ||
+ BaseD->getType()->isReferenceType()) ||
+ MutableSubobject)
+ return false;
+ return declaresSameEntity(Evaluating, BaseD);
+ }
+
+ llvm_unreachable("unknown evaluating decl kind");
}
namespace {
@@ -2986,13 +3021,13 @@ struct CompleteObject {
CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type)
: Base(Base), Value(Value), Type(Type) {}
- bool mayReadMutableMembers(EvalInfo &Info) const {
+ bool mayAccessMutableMembers(EvalInfo &Info, AccessKinds AK) const {
// In C++14 onwards, it is permitted to read a mutable member whose
// lifetime began within the evaluation.
// FIXME: Should we also allow this in C++11?
if (!Info.getLangOpts().CPlusPlus14)
return false;
- return lifetimeStartedInEvaluation(Info, Base);
+ return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true);
}
explicit operator bool() const { return !Type.isNull(); }
@@ -3097,9 +3132,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
// things we need to check: if there are any mutable subobjects, we
// cannot perform this read. (This only happens when performing a trivial
// copy or assignment.)
- if (ObjType->isRecordType() && isRead(handler.AccessKind) &&
- !Obj.mayReadMutableMembers(Info) &&
- diagnoseUnreadableFields(Info, E, ObjType))
+ if (ObjType->isRecordType() &&
+ !Obj.mayAccessMutableMembers(Info, handler.AccessKind) &&
+ diagnoseMutableFields(Info, E, handler.AccessKind, ObjType))
return handler.failed();
}
@@ -3167,10 +3202,10 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
: O->getComplexFloatReal(), ObjType);
}
} else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) {
- if (Field->isMutable() && isRead(handler.AccessKind) &&
- !Obj.mayReadMutableMembers(Info)) {
- Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1)
- << Field;
+ if (Field->isMutable() &&
+ !Obj.mayAccessMutableMembers(Info, handler.AccessKind)) {
+ Info.FFDiag(E, diag::note_constexpr_access_mutable, 1)
+ << handler.AccessKind << Field;
Info.Note(Field->getLocation(), diag::note_declared_at);
return handler.failed();
}
@@ -3427,8 +3462,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
// the variable we're reading must be const.
if (!Frame) {
if (Info.getLangOpts().CPlusPlus14 &&
- declaresSameEntity(
- VD, Info.EvaluatingDecl.dyn_cast<const ValueDecl *>())) {
+ lifetimeStartedInEvaluation(Info, LVal.Base)) {
// OK, we can read and modify an object if we're in the process of
// evaluating its initializer, because its lifetime began in this
// evaluation.
@@ -3518,11 +3552,14 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
// int x = ++r;
// constexpr int k = r;
// Therefore we use the C++14 rules in C++11 too.
- const ValueDecl *VD = Info.EvaluatingDecl.dyn_cast<const ValueDecl*>();
- const ValueDecl *ED = MTE->getExtendingDecl();
+ //
+ // Note that temporaries whose lifetimes began while evaluating a
+ // variable's constructor are not usable while evaluating the
+ // corresponding destructor, not even if they're of const-qualified
+ // types.
if (!(BaseType.isConstQualified() &&
BaseType->isIntegralOrEnumerationType()) &&
- !(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) {
+ !lifetimeStartedInEvaluation(Info, LVal.Base)) {
if (!IsAccess)
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
Info.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
@@ -13282,6 +13319,41 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
CheckMemoryLeaks(Info);
}
+bool VarDecl::evaluateDestruction(
+ SmallVectorImpl<PartialDiagnosticAt> &Notes) const {
+ assert(getEvaluatedValue() && !getEvaluatedValue()->isAbsent() &&
+ "cannot evaluate destruction of non-constant-initialized variable");
+
+ Expr::EvalStatus EStatus;
+ EStatus.Diag = &Notes;
+
+ // Make a copy of the value for the destructor to mutate.
+ APValue DestroyedValue = *getEvaluatedValue();
+
+ EvalInfo Info(getASTContext(), EStatus, EvalInfo::EM_ConstantExpression);
+ Info.setEvaluatingDecl(this, DestroyedValue,
+ EvalInfo::EvaluatingDeclKind::Dtor);
+ Info.InConstantContext = true;
+
+ SourceLocation DeclLoc = getLocation();
+ QualType DeclTy = getType();
+
+ LValue LVal;
+ LVal.set(this);
+
+ // FIXME: Consider storing whether this variable has constant destruction in
+ // the EvaluatedStmt so that CodeGen can query it.
+ if (!HandleDestruction(Info, DeclLoc, LVal.Base, DestroyedValue, DeclTy) ||
+ EStatus.HasSideEffects)
+ return false;
+
+ if (!Info.discardCleanups())
+ llvm_unreachable("Unhandled cleanup; missing full expression marker?");
+
+ ensureEvaluatedStmt()->HasConstantDestruction = true;
+ return true;
+}
+
/// isEvaluatable - Call EvaluateAsRValue to see if this expression can be
/// constant folded, but discard the result.
bool Expr::isEvaluatable(const ASTContext &Ctx, SideEffectsKind SEK) const {
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
index c9ace131bbd..1a8109cedf7 100644
--- a/clang/lib/AST/Interp/Interp.cpp
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -275,7 +275,7 @@ bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
const FieldDecl *Field = Ptr.getField();
- S.FFDiag(Loc, diag::note_constexpr_ltor_mutable, 1) << Field;
+ S.FFDiag(Loc, diag::note_constexpr_access_mutable, 1) << AK_Read << Field;
S.Note(Field->getLocation(), diag::note_declared_at);
return false;
}
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index d1915fdad2e..a540346ad45 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -1384,6 +1384,8 @@ void TextNodeDumper::VisitVarDecl(const VarDecl *D) {
break;
}
}
+ if (D->needsDestruction(D->getASTContext()))
+ OS << " destroyed";
if (D->isParameterPack())
OS << " pack";
}
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 7fa262ca6e8..0a0ba70d822 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -3093,7 +3093,7 @@ void CodeGenFunction::EmitDelegateCallArg(CallArgList &args,
// Deactivate the cleanup for the callee-destructed param that was pushed.
if (hasAggregateEvaluationKind(type) && !CurFuncIsThunk &&
type->getAs<RecordType>()->getDecl()->isParamDestroyedInCallee() &&
- type.isDestructedType()) {
+ param->needsDestruction(getContext())) {
EHScopeStack::stable_iterator cleanup =
CalleeDestructedParamCleanups.lookup(cast<ParmVarDecl>(param));
assert(cleanup.isValid() &&
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index c8bb63c5c4b..76ab8852397 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -2083,7 +2083,7 @@ static bool canEmitDelegateCallArgs(CodeGenFunction &CGF,
if (CGF.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
// If the parameters are callee-cleanup, it's not safe to forward.
for (auto *P : Ctor->parameters())
- if (P->getType().isDestructedType())
+ if (P->needsDestruction(CGF.getContext()))
return false;
// Likewise if they're inalloca.
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index a9e01619aab..5c0d52a1633 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -305,14 +305,6 @@ llvm::Constant *CodeGenModule::getOrCreateStaticVarDecl(
return Addr;
}
-/// hasNontrivialDestruction - Determine whether a type's destruction is
-/// non-trivial. If so, and the variable uses static initialization, we must
-/// register its destructor to run on exit.
-static bool hasNontrivialDestruction(QualType T) {
- CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
- return RD && !RD->hasTrivialDestructor();
-}
-
/// AddInitializerToStaticVarDecl - Add the initializer for 'D' to the
/// global variable that has already been created for it. If the initializer
/// has a different type than GV does, this may free GV and return a different
@@ -372,7 +364,7 @@ CodeGenFunction::AddInitializerToStaticVarDecl(const VarDecl &D,
emitter.finalize(GV);
- if (hasNontrivialDestruction(D.getType()) && HaveInsertPoint()) {
+ if (D.needsDestruction(getContext()) && HaveInsertPoint()) {
// We have a constant initializer, but a nontrivial destructor. We still
// need to perform a guarded "initialization" in order to register the
// destructor.
@@ -1994,7 +1986,7 @@ void CodeGenFunction::EmitAutoVarCleanups(const AutoVarEmission &emission) {
const VarDecl &D = *emission.Variable;
// Check the type for a cleanup.
- if (QualType::DestructionKind dtorKind = D.getType().isDestructedType())
+ if (QualType::DestructionKind dtorKind = D.needsDestruction(getContext()))
emitAutoVarTypeCleanup(emission, dtorKind);
// In GC mode, honor objc_precise_lifetime.
@@ -2404,7 +2396,8 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, ParamValue Arg,
// cleanup.
if (hasAggregateEvaluationKind(Ty) && !CurFuncIsThunk &&
Ty->getAs<RecordType>()->getDecl()->isParamDestroyedInCallee()) {
- if (QualType::DestructionKind DtorKind = Ty.isDestructedType()) {
+ if (QualType::DestructionKind DtorKind =
+ D.needsDestruction(getContext())) {
assert((DtorKind == QualType::DK_cxx_destructor ||
DtorKind == QualType::DK_nontrivial_c_struct) &&
"unexpected destructor type");
diff --git a/clang/lib/CodeGen/CGDeclCXX.cpp b/clang/lib/CodeGen/CGDeclCXX.cpp
index 7a0605b8450..a54e5dcfda2 100644
--- a/clang/lib/CodeGen/CGDeclCXX.cpp
+++ b/clang/lib/CodeGen/CGDeclCXX.cpp
@@ -73,16 +73,10 @@ static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D,
// that isn't balanced out by a destructor call as intended by the
// attribute. This also checks for -fno-c++-static-destructors and
// bails even if the attribute is not present.
- if (D.isNoDestroy(CGF.getContext()))
- return;
-
- CodeGenModule &CGM = CGF.CGM;
+ QualType::DestructionKind DtorKind = D.needsDestruction(CGF.getContext());
// FIXME: __attribute__((cleanup)) ?
- QualType Type = D.getType();
- QualType::DestructionKind DtorKind = Type.isDestructedType();
-
switch (DtorKind) {
case QualType::DK_none:
return;
@@ -101,6 +95,9 @@ static void EmitDeclDestroy(CodeGenFunction &CGF, const VarDecl &D,
llvm::FunctionCallee Func;
llvm::Constant *Argument;
+ CodeGenModule &CGM = CGF.CGM;
+ QualType Type = D.getType();
+
// Special-case non-array C++ destructors, if they have the right signature.
// Under some ABIs, destructors return this instead of void, and cannot be
// passed directly to __cxa_atexit if the target does not allow this
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 486832bca5f..37e2533e976 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -3809,9 +3809,9 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
return;
llvm::Constant *Init = nullptr;
- CXXRecordDecl *RD = ASTTy->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
bool NeedsGlobalCtor = false;
- bool NeedsGlobalDtor = RD && !RD->hasTrivialDestructor();
+ bool NeedsGlobalDtor =
+ D->needsDestruction(getContext()) == QualType::DK_cxx_destructor;
const VarDecl *InitDecl;
const Expr *InitExpr = D->getAnyInitializer(InitDecl);
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 037185390c9..35e707243ea 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -350,7 +350,7 @@ public:
// If we have the only definition, we don't need a thread wrapper if we
// will emit the value as a constant.
if (isUniqueGVALinkage(getContext().GetGVALinkageForVariable(VD)))
- return !VD->getType().isDestructedType() && InitDecl->evaluateValue();
+ return !VD->needsDestruction(getContext()) && InitDecl->evaluateValue();
// Otherwise, we need a thread wrapper unless we know that every
// translation unit will emit the value as a constant. We rely on
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c03e4dc66f8..d21d2689123 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -13398,6 +13398,19 @@ void Sema::FinalizeVarWithDestructor(VarDecl *VD, const RecordType *Record) {
}
if (Destructor->isTrivial()) return;
+
+ // If the destructor is constexpr, check whether the variable has constant
+ // destruction now.
+ if (Destructor->isConstexpr() && VD->evaluateValue()) {
+ SmallVector<PartialDiagnosticAt, 8> Notes;
+ if (!VD->evaluateDestruction(Notes) && VD->isConstexpr()) {
+ Diag(VD->getLocation(),
+ diag::err_constexpr_var_requires_const_destruction) << VD;
+ for (unsigned I = 0, N = Notes.size(); I != N; ++I)
+ Diag(Notes[I].first, Notes[I].second);
+ }
+ }
+
if (!VD->hasGlobalStorage()) return;
// Emit warning for non-trivial dtor in global scope (a real global,
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index e9cf366983f..76928182a1e 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1390,10 +1390,11 @@ ASTDeclReader::RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
if (uint64_t Val = Record.readInt()) {
VD->setInit(Record.readExpr());
- if (Val > 1) { // IsInitKnownICE = 1, IsInitNotICE = 2, IsInitICE = 3
+ if (Val > 1) {
EvaluatedStmt *Eval = VD->ensureEvaluatedStmt();
Eval->CheckedICE = true;
- Eval->IsICE = Val == 3;
+ Eval->IsICE = (Val & 1) != 0;
+ Eval->HasConstantDestruction = (Val & 4) != 0;
}
}
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index b71315505de..2c22587957e 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -968,7 +968,14 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
Record.push_back(D->getLinkageInternal());
if (D->getInit()) {
- Record.push_back(!D->isInitKnownICE() ? 1 : (D->isInitICE() ? 3 : 2));
+ if (!D->isInitKnownICE())
+ Record.push_back(1);
+ else {
+ Record.push_back(
+ 2 |
+ (D->isInitICE() ? 1 : 0) |
+ (D->ensureEvaluatedStmt()->HasConstantDestruction ? 4 : 0));
+ }
Record.AddStmt(D->getInit());
} else {
Record.push_back(0);
@@ -2140,7 +2147,7 @@ void ASTWriter::WriteDeclAbbrevs() {
Abv->Add(BitCodeAbbrevOp(0)); // ImplicitParamKind
Abv->Add(BitCodeAbbrevOp(0)); // EscapingByref
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // Linkage
- Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2)); // IsInitICE (local)
+ Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 3)); // IsInitICE (local)
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 2)); // VarKind (local enum)
// Type Source Info
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array));
diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp
index 0aaedcc0769..8d51dbde717 100644
--- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p9.cpp
@@ -1,4 +1,5 @@
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++2a %s
// A constexpr specifier used in an object declaration declares the object as
// const.
@@ -35,3 +36,19 @@ struct pixel {
};
constexpr pixel ur = { 1294, 1024 }; // ok
constexpr pixel origin; // expected-error {{default initialization of an object of const type 'const pixel' without a user-provided default constructor}}
+
+#if __cplusplus > 201702L
+// A constexpr variable shall have constant destruction.
+struct A {
+ bool ok;
+ constexpr A(bool ok) : ok(ok) {}
+ constexpr ~A() noexcept(false) {
+ void oops(); // expected-note 2{{declared here}}
+ if (!ok) oops(); // expected-note 2{{non-constexpr function}}
+ }
+};
+
+constexpr A const_dtor(true);
+constexpr A non_const_dtor(false); // expected-error {{must have constant destruction}} expected-note {{in call}}
+constexpr A arr_dtor[5] = {true, true, true, false, true}; // expected-error {{must have constant destruction}} expected-note {{in call to '&arr_dtor[3]->~A()'}}
+#endif
diff --git a/clang/test/CXX/expr/expr.const/p6-2a.cpp b/clang/test/CXX/expr/expr.const/p6-2a.cpp
new file mode 100644
index 00000000000..312c2835418
--- /dev/null
+++ b/clang/test/CXX/expr/expr.const/p6-2a.cpp
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 -std=c++2a -verify %s
+
+constexpr int non_class = 42;
+constexpr int arr_non_class[5] = {1, 2, 3};
+
+struct A {
+ int member = 1;
+ constexpr ~A() { member = member + 1; }
+};
+constexpr A class_ = {};
+constexpr A arr_class[5] = {{}, {}};
+
+struct Mutable {
+ mutable int member = 1; // expected-note {{declared here}}
+ constexpr ~Mutable() { member = member + 1; } // expected-note {{read of mutable member}}
+};
+constexpr Mutable mut_member; // expected-error {{must have constant destruction}} expected-note {{in call}}
+
+struct MutableStore {
+ mutable int member = 1; // expected-note {{declared here}}
+ constexpr ~MutableStore() { member = 2; } // expected-note {{assignment to mutable member}}
+};
+constexpr MutableStore mut_store; // expected-error {{must have constant destruction}} expected-note {{in call}}
+
+// Note: the constant destruction rules disallow this example even though hcm.n is a const object.
+struct MutableConst {
+ struct HasConstMember {
+ const int n = 4;
+ };
+ mutable HasConstMember hcm; // expected-note {{here}}
+ constexpr ~MutableConst() {
+ int q = hcm.n; // expected-note {{read of mutable}}
+ }
+};
+constexpr MutableConst mc; // expected-error {{must have constant destruction}} expected-note {{in call}}
+
+struct Temporary {
+ int &&temp;
+ constexpr ~Temporary() {
+ int n = temp; // expected-note {{outside the expression that created the temporary}}
+ }
+};
+constexpr Temporary t = {3}; // expected-error {{must have constant destruction}} expected-note {{created here}} expected-note {{in call}}
diff --git a/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp b/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp
index 2e004d6426d..b03791e5135 100644
--- a/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp
+++ b/clang/test/CodeGenCXX/attr-no-destroy-d54344.cpp
@@ -14,6 +14,7 @@
class a {
public:
+ a();
~a();
};
class logger_base {
diff --git a/clang/test/CodeGenCXX/const-init-cxx2a.cpp b/clang/test/CodeGenCXX/const-init-cxx2a.cpp
index 499a16ea6dd..1195b912c25 100644
--- a/clang/test/CodeGenCXX/const-init-cxx2a.cpp
+++ b/clang/test/CodeGenCXX/const-init-cxx2a.cpp
@@ -1,5 +1,58 @@
-// RUN: %clang_cc1 -verify -triple x86_64-apple-darwin -emit-llvm -o - %s -std=c++2a | FileCheck %s
-// expected-no-diagnostics
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s -std=c++2a | FileCheck %s --implicit-check-not=cxx_global_var_init --implicit-check-not=cxa_atexit
+
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-pch -o %t.pch %s -std=c++2a
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -include-pch %t.pch -x c++ /dev/null -emit-llvm -o - -std=c++2a | FileCheck %s --implicit-check-not=cxx_global_var_init --implicit-check-not=cxa_atexit
// CHECK: @a = global i32 123,
int a = (delete new int, 123);
+
+struct B {
+ constexpr B() {}
+ constexpr ~B() { n *= 5; }
+ int n = 123;
+};
+// CHECK: @b = global {{.*}} i32 123
+extern constexpr B b = B();
+
+// CHECK: @_ZL1c = internal global {{.*}} i32 123
+const B c;
+int use_c() { return c.n; }
+
+struct D {
+ int n;
+ constexpr ~D() {}
+};
+D d;
+// CHECK: @d = global {{.*}} zeroinitializer
+
+D d_arr[3];
+// CHECK: @d_arr = global {{.*}} zeroinitializer
+
+thread_local D d_tl;
+// CHECK: @d_tl = thread_local global {{.*}} zeroinitializer
+
+// CHECK-NOT: @llvm.global_ctors
+
+// CHECK-LABEL: define {{.*}} @_Z1fv(
+void f() {
+ // CHECK-NOT: call
+ // CHECK: call {{.*}}memcpy
+ // CHECK-NOT: call
+ // CHECK: call {{.*}}memset
+ // CHECK-NOT: call
+ // CHECK: }
+ constexpr B b;
+ D d = D();
+}
+
+// CHECK-LABEL: define {{.*}} @_Z1gv(
+void g() {
+ // CHECK-NOT: call
+ // CHECK-NOT: cxa_guard
+ // CHECK-NOT: _ZGV
+ // CHECK: }
+ static constexpr B b1;
+ static const B b2;
+ static D d;
+ thread_local D d_tl;
+}
diff --git a/clang/test/CodeGenCXX/no_destroy.cpp b/clang/test/CodeGenCXX/no_destroy.cpp
index 3400b6080b5..607cbfb3a1f 100644
--- a/clang/test/CodeGenCXX/no_destroy.cpp
+++ b/clang/test/CodeGenCXX/no_destroy.cpp
@@ -5,10 +5,8 @@ struct NonTrivial {
~NonTrivial();
};
-// CHECK-LABEL: define internal void @__cxx_global_var_init
// CHECK-NOT: __cxa_atexit{{.*}}_ZN10NonTrivialD1Ev
[[clang::no_destroy]] NonTrivial nt1;
-// CHECK-LABEL: define internal void @__cxx_global_var_init
// CHECK-NOT: _tlv_atexit{{.*}}_ZN10NonTrivialD1Ev
[[clang::no_destroy]] thread_local NonTrivial nt2;
@@ -16,11 +14,9 @@ struct NonTrivial2 {
~NonTrivial2();
};
-// CHECK-LABEL: define internal void @__cxx_global_var_init
-// CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev
+// CHECK: __cxa_atexit{{.*}}_ZN11NonTrivial2D1Ev{{.*}}nt21
NonTrivial2 nt21;
-// CHECK-LABEL: define internal void @__cxx_global_var_init
-// CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev
+// CHECK: _tlv_atexit{{.*}}_ZN11NonTrivial2D1Ev{{.*}}nt22
thread_local NonTrivial2 nt22;
// CHECK-LABEL: define void @_Z1fv
diff --git a/clang/test/CodeGenCXX/non-const-init-cxx2a.cpp b/clang/test/CodeGenCXX/non-const-init-cxx2a.cpp
new file mode 100644
index 00000000000..120b32090fb
--- /dev/null
+++ b/clang/test/CodeGenCXX/non-const-init-cxx2a.cpp
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -o - %s -std=c++2a | FileCheck %s
+
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-pch -o %t.pch %s -std=c++2a
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -include-pch %t.pch -x c++ /dev/null -emit-llvm -o - -std=c++2a | FileCheck %s
+
+struct B {
+ constexpr B() {}
+ constexpr ~B() { n *= 5; }
+ int n = 123;
+};
+
+// We emit a dynamic destructor here because b.n might have been modified
+// before b is destroyed.
+//
+// CHECK: @b = global {{.*}} i32 123
+B b = B();
+
+// CHECK: define {{.*}}cxx_global_var_init
+// CHECK: call {{.*}} @__cxa_atexit({{.*}} @_ZN1BD1Ev {{.*}} @b
OpenPOWER on IntegriCloud