Skip to content

Commit 2b4fa53

Browse files
committed
For P0784R7: compute whether a variable has constant destruction if it
has a constexpr destructor. For constexpr variables, reject if the variable does not have constant destruction. In all cases, do not emit runtime calls to the destructor for variables with constant destruction. llvm-svn: 373159
1 parent ac59699 commit 2b4fa53

23 files changed

+325
-75
lines changed

clang/include/clang/AST/Decl.h

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -808,12 +808,19 @@ struct EvaluatedStmt {
808808
/// valid if CheckedICE is true.
809809
bool IsICE : 1;
810810

811+
/// Whether this variable is known to have constant destruction. That is,
812+
/// whether running the destructor on the initial value is a side-effect
813+
/// (and doesn't inspect any state that might have changed during program
814+
/// execution). This is currently only computed if the destructor is
815+
/// non-trivial.
816+
bool HasConstantDestruction : 1;
817+
811818
Stmt *Value;
812819
APValue Evaluated;
813820

814-
EvaluatedStmt() : WasEvaluated(false), IsEvaluating(false), CheckedICE(false),
815-
CheckingICE(false), IsICE(false) {}
816-
821+
EvaluatedStmt()
822+
: WasEvaluated(false), IsEvaluating(false), CheckedICE(false),
823+
CheckingICE(false), IsICE(false), HasConstantDestruction(false) {}
817824
};
818825

819826
/// Represents a variable declaration or definition.
@@ -1267,6 +1274,14 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
12671274
/// to untyped APValue if the value could not be evaluated.
12681275
APValue *getEvaluatedValue() const;
12691276

1277+
/// Evaluate the destruction of this variable to determine if it constitutes
1278+
/// constant destruction.
1279+
///
1280+
/// \pre isInitICE()
1281+
/// \return \c true if this variable has constant destruction, \c false if
1282+
/// not.
1283+
bool evaluateDestruction(SmallVectorImpl<PartialDiagnosticAt> &Notes) const;
1284+
12701285
/// Determines whether it is already known whether the
12711286
/// initializer is an integral constant expression or not.
12721287
bool isInitKnownICE() const;
@@ -1505,9 +1520,14 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
15051520
// has no definition within this source file.
15061521
bool isKnownToBeDefined() const;
15071522

1508-
/// Do we need to emit an exit-time destructor for this variable?
1523+
/// Is destruction of this variable entirely suppressed? If so, the variable
1524+
/// need not have a usable destructor at all.
15091525
bool isNoDestroy(const ASTContext &) const;
15101526

1527+
/// Do we need to emit an exit-time destructor for this variable, and if so,
1528+
/// what kind?
1529+
QualType::DestructionKind needsDestruction(const ASTContext &Ctx) const;
1530+
15111531
// Implement isa/cast/dyncast/etc.
15121532
static bool classof(const Decl *D) { return classofKind(D->getKind()); }
15131533
static bool classofKind(Kind K) { return K >= firstVar && K <= lastVar; }

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ def note_constexpr_access_volatile_obj : Note<
145145
"a constant expression">;
146146
def note_constexpr_volatile_here : Note<
147147
"volatile %select{temporary created|object declared|member declared}0 here">;
148-
def note_constexpr_ltor_mutable : Note<
149-
"read of mutable member %0 is not allowed in a constant expression">;
148+
def note_constexpr_access_mutable : Note<
149+
"%select{read of|read of|assignment to|increment of|decrement of|"
150+
"member call on|dynamic_cast of|typeid applied to|destruction of}0 "
151+
"mutable member %1 is not allowed in a constant expression">;
150152
def note_constexpr_ltor_non_const_int : Note<
151153
"read of non-const variable %0 is not allowed in a constant expression">;
152154
def note_constexpr_ltor_non_constexpr : Note<

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2384,6 +2384,8 @@ def err_constexpr_var_non_literal : Error<
23842384
"constexpr variable cannot have non-literal type %0">;
23852385
def err_constexpr_var_requires_const_init : Error<
23862386
"constexpr variable %0 must be initialized by a constant expression">;
2387+
def err_constexpr_var_requires_const_destruction : Error<
2388+
"constexpr variable %0 must have constant destruction">;
23872389
def err_constexpr_redecl_mismatch : Error<
23882390
"%select{non-constexpr|constexpr|consteval}1 declaration of %0"
23892391
" follows %select{non-constexpr|constexpr|consteval}2 declaration">;

clang/lib/AST/ASTContext.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10064,7 +10064,7 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) {
1006410064
return false;
1006510065

1006610066
// Variables that have destruction with side-effects are required.
10067-
if (VD->getType().isDestructedType())
10067+
if (VD->needsDestruction(*this))
1006810068
return true;
1006910069

1007010070
// Variables that have initialization with side-effects are required.

clang/lib/AST/Decl.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,6 +2592,18 @@ bool VarDecl::isNoDestroy(const ASTContext &Ctx) const {
25922592
!hasAttr<AlwaysDestroyAttr>()));
25932593
}
25942594

2595+
QualType::DestructionKind
2596+
VarDecl::needsDestruction(const ASTContext &Ctx) const {
2597+
if (EvaluatedStmt *Eval = Init.dyn_cast<EvaluatedStmt *>())
2598+
if (Eval->HasConstantDestruction)
2599+
return QualType::DK_none;
2600+
2601+
if (isNoDestroy(Ctx))
2602+
return QualType::DK_none;
2603+
2604+
return getType().isDestructedType();
2605+
}
2606+
25952607
MemberSpecializationInfo *VarDecl::getMemberSpecializationInfo() const {
25962608
if (isStaticDataMember())
25972609
// FIXME: Remove ?

clang/lib/AST/ExprConstant.cpp

Lines changed: 104 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,15 @@ namespace {
744744
/// evaluated, if any.
745745
APValue::LValueBase EvaluatingDecl;
746746

747+
enum class EvaluatingDeclKind {
748+
None,
749+
/// We're evaluating the construction of EvaluatingDecl.
750+
Ctor,
751+
/// We're evaluating the destruction of EvaluatingDecl.
752+
Dtor,
753+
};
754+
EvaluatingDeclKind IsEvaluatingDecl = EvaluatingDeclKind::None;
755+
747756
/// EvaluatingDeclValue - This is the value being constructed for the
748757
/// declaration whose initializer is being evaluated, if any.
749758
APValue *EvaluatingDeclValue;
@@ -902,8 +911,10 @@ namespace {
902911
discardCleanups();
903912
}
904913

905-
void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value) {
914+
void setEvaluatingDecl(APValue::LValueBase Base, APValue &Value,
915+
EvaluatingDeclKind EDK = EvaluatingDeclKind::Ctor) {
906916
EvaluatingDecl = Base;
917+
IsEvaluatingDecl = EDK;
907918
EvaluatingDeclValue = &Value;
908919
}
909920

@@ -2913,8 +2924,8 @@ static bool isReadByLvalueToRvalueConversion(QualType T) {
29132924

29142925
/// Diagnose an attempt to read from any unreadable field within the specified
29152926
/// type, which might be a class type.
2916-
static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E,
2917-
QualType T) {
2927+
static bool diagnoseMutableFields(EvalInfo &Info, const Expr *E, AccessKinds AK,
2928+
QualType T) {
29182929
CXXRecordDecl *RD = T->getBaseElementTypeUnsafe()->getAsCXXRecordDecl();
29192930
if (!RD)
29202931
return false;
@@ -2929,25 +2940,26 @@ static bool diagnoseUnreadableFields(EvalInfo &Info, const Expr *E,
29292940
// FIXME: Add core issue number for the union case.
29302941
if (Field->isMutable() &&
29312942
(RD->isUnion() || isReadByLvalueToRvalueConversion(Field->getType()))) {
2932-
Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1) << Field;
2943+
Info.FFDiag(E, diag::note_constexpr_access_mutable, 1) << AK << Field;
29332944
Info.Note(Field->getLocation(), diag::note_declared_at);
29342945
return true;
29352946
}
29362947

2937-
if (diagnoseUnreadableFields(Info, E, Field->getType()))
2948+
if (diagnoseMutableFields(Info, E, AK, Field->getType()))
29382949
return true;
29392950
}
29402951

29412952
for (auto &BaseSpec : RD->bases())
2942-
if (diagnoseUnreadableFields(Info, E, BaseSpec.getType()))
2953+
if (diagnoseMutableFields(Info, E, AK, BaseSpec.getType()))
29432954
return true;
29442955

29452956
// All mutable fields were empty, and thus not actually read.
29462957
return false;
29472958
}
29482959

29492960
static bool lifetimeStartedInEvaluation(EvalInfo &Info,
2950-
APValue::LValueBase Base) {
2961+
APValue::LValueBase Base,
2962+
bool MutableSubobject = false) {
29512963
// A temporary we created.
29522964
if (Base.getCallIndex())
29532965
return true;
@@ -2956,19 +2968,42 @@ static bool lifetimeStartedInEvaluation(EvalInfo &Info,
29562968
if (!Evaluating)
29572969
return false;
29582970

2959-
// The variable whose initializer we're evaluating.
2960-
if (auto *BaseD = Base.dyn_cast<const ValueDecl*>())
2961-
if (declaresSameEntity(Evaluating, BaseD))
2962-
return true;
2971+
auto *BaseD = Base.dyn_cast<const ValueDecl*>();
29632972

2964-
// A temporary lifetime-extended by the variable whose initializer we're
2965-
// evaluating.
2966-
if (auto *BaseE = Base.dyn_cast<const Expr *>())
2967-
if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE))
2968-
if (declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating))
2969-
return true;
2973+
switch (Info.IsEvaluatingDecl) {
2974+
case EvalInfo::EvaluatingDeclKind::None:
2975+
return false;
29702976

2971-
return false;
2977+
case EvalInfo::EvaluatingDeclKind::Ctor:
2978+
// The variable whose initializer we're evaluating.
2979+
if (BaseD)
2980+
return declaresSameEntity(Evaluating, BaseD);
2981+
2982+
// A temporary lifetime-extended by the variable whose initializer we're
2983+
// evaluating.
2984+
if (auto *BaseE = Base.dyn_cast<const Expr *>())
2985+
if (auto *BaseMTE = dyn_cast<MaterializeTemporaryExpr>(BaseE))
2986+
return declaresSameEntity(BaseMTE->getExtendingDecl(), Evaluating);
2987+
return false;
2988+
2989+
case EvalInfo::EvaluatingDeclKind::Dtor:
2990+
// C++2a [expr.const]p6:
2991+
// [during constant destruction] the lifetime of a and its non-mutable
2992+
// subobjects (but not its mutable subobjects) [are] considered to start
2993+
// within e.
2994+
//
2995+
// FIXME: We can meaningfully extend this to cover non-const objects, but
2996+
// we will need special handling: we should be able to access only
2997+
// subobjects of such objects that are themselves declared const.
2998+
if (!BaseD ||
2999+
!(BaseD->getType().isConstQualified() ||
3000+
BaseD->getType()->isReferenceType()) ||
3001+
MutableSubobject)
3002+
return false;
3003+
return declaresSameEntity(Evaluating, BaseD);
3004+
}
3005+
3006+
llvm_unreachable("unknown evaluating decl kind");
29723007
}
29733008

29743009
namespace {
@@ -2986,13 +3021,13 @@ struct CompleteObject {
29863021
CompleteObject(APValue::LValueBase Base, APValue *Value, QualType Type)
29873022
: Base(Base), Value(Value), Type(Type) {}
29883023

2989-
bool mayReadMutableMembers(EvalInfo &Info) const {
3024+
bool mayAccessMutableMembers(EvalInfo &Info, AccessKinds AK) const {
29903025
// In C++14 onwards, it is permitted to read a mutable member whose
29913026
// lifetime began within the evaluation.
29923027
// FIXME: Should we also allow this in C++11?
29933028
if (!Info.getLangOpts().CPlusPlus14)
29943029
return false;
2995-
return lifetimeStartedInEvaluation(Info, Base);
3030+
return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true);
29963031
}
29973032

29983033
explicit operator bool() const { return !Type.isNull(); }
@@ -3097,9 +3132,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
30973132
// things we need to check: if there are any mutable subobjects, we
30983133
// cannot perform this read. (This only happens when performing a trivial
30993134
// copy or assignment.)
3100-
if (ObjType->isRecordType() && isRead(handler.AccessKind) &&
3101-
!Obj.mayReadMutableMembers(Info) &&
3102-
diagnoseUnreadableFields(Info, E, ObjType))
3135+
if (ObjType->isRecordType() &&
3136+
!Obj.mayAccessMutableMembers(Info, handler.AccessKind) &&
3137+
diagnoseMutableFields(Info, E, handler.AccessKind, ObjType))
31033138
return handler.failed();
31043139
}
31053140

@@ -3167,10 +3202,10 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
31673202
: O->getComplexFloatReal(), ObjType);
31683203
}
31693204
} else if (const FieldDecl *Field = getAsField(Sub.Entries[I])) {
3170-
if (Field->isMutable() && isRead(handler.AccessKind) &&
3171-
!Obj.mayReadMutableMembers(Info)) {
3172-
Info.FFDiag(E, diag::note_constexpr_ltor_mutable, 1)
3173-
<< Field;
3205+
if (Field->isMutable() &&
3206+
!Obj.mayAccessMutableMembers(Info, handler.AccessKind)) {
3207+
Info.FFDiag(E, diag::note_constexpr_access_mutable, 1)
3208+
<< handler.AccessKind << Field;
31743209
Info.Note(Field->getLocation(), diag::note_declared_at);
31753210
return handler.failed();
31763211
}
@@ -3427,8 +3462,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
34273462
// the variable we're reading must be const.
34283463
if (!Frame) {
34293464
if (Info.getLangOpts().CPlusPlus14 &&
3430-
declaresSameEntity(
3431-
VD, Info.EvaluatingDecl.dyn_cast<const ValueDecl *>())) {
3465+
lifetimeStartedInEvaluation(Info, LVal.Base)) {
34323466
// OK, we can read and modify an object if we're in the process of
34333467
// evaluating its initializer, because its lifetime began in this
34343468
// evaluation.
@@ -3518,11 +3552,14 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
35183552
// int x = ++r;
35193553
// constexpr int k = r;
35203554
// Therefore we use the C++14 rules in C++11 too.
3521-
const ValueDecl *VD = Info.EvaluatingDecl.dyn_cast<const ValueDecl*>();
3522-
const ValueDecl *ED = MTE->getExtendingDecl();
3555+
//
3556+
// Note that temporaries whose lifetimes began while evaluating a
3557+
// variable's constructor are not usable while evaluating the
3558+
// corresponding destructor, not even if they're of const-qualified
3559+
// types.
35233560
if (!(BaseType.isConstQualified() &&
35243561
BaseType->isIntegralOrEnumerationType()) &&
3525-
!(VD && VD->getCanonicalDecl() == ED->getCanonicalDecl())) {
3562+
!lifetimeStartedInEvaluation(Info, LVal.Base)) {
35263563
if (!IsAccess)
35273564
return CompleteObject(LVal.getLValueBase(), nullptr, BaseType);
35283565
Info.FFDiag(E, diag::note_constexpr_access_static_temporary, 1) << AK;
@@ -13282,6 +13319,41 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx,
1328213319
CheckMemoryLeaks(Info);
1328313320
}
1328413321

13322+
bool VarDecl::evaluateDestruction(
13323+
SmallVectorImpl<PartialDiagnosticAt> &Notes) const {
13324+
assert(getEvaluatedValue() && !getEvaluatedValue()->isAbsent() &&
13325+
"cannot evaluate destruction of non-constant-initialized variable");
13326+
13327+
Expr::EvalStatus EStatus;
13328+
EStatus.Diag = &Notes;
13329+
13330+
// Make a copy of the value for the destructor to mutate.
13331+
APValue DestroyedValue = *getEvaluatedValue();
13332+
13333+
EvalInfo Info(getASTContext(), EStatus, EvalInfo::EM_ConstantExpression);
13334+
Info.setEvaluatingDecl(this, DestroyedValue,
13335+
EvalInfo::EvaluatingDeclKind::Dtor);
13336+
Info.InConstantContext = true;
13337+
13338+
SourceLocation DeclLoc = getLocation();
13339+
QualType DeclTy = getType();
13340+
13341+
LValue LVal;
13342+
LVal.set(this);
13343+
13344+
// FIXME: Consider storing whether this variable has constant destruction in
13345+
// the EvaluatedStmt so that CodeGen can query it.
13346+
if (!HandleDestruction(Info, DeclLoc, LVal.Base, DestroyedValue, DeclTy) ||
13347+
EStatus.HasSideEffects)
13348+
return false;
13349+
13350+
if (!Info.discardCleanups())
13351+
llvm_unreachable("Unhandled cleanup; missing full expression marker?");
13352+
13353+
ensureEvaluatedStmt()->HasConstantDestruction = true;
13354+
return true;
13355+
}
13356+
1328513357
/// isEvaluatable - Call EvaluateAsRValue to see if this expression can be
1328613358
/// constant folded, but discard the result.
1328713359
bool Expr::isEvaluatable(const ASTContext &Ctx, SideEffectsKind SEK) const {

clang/lib/AST/Interp/Interp.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ bool CheckMutable(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
275275

276276
const SourceInfo &Loc = S.Current->getSource(OpPC);
277277
const FieldDecl *Field = Ptr.getField();
278-
S.FFDiag(Loc, diag::note_constexpr_ltor_mutable, 1) << Field;
278+
S.FFDiag(Loc, diag::note_constexpr_access_mutable, 1) << AK_Read << Field;
279279
S.Note(Field->getLocation(), diag::note_declared_at);
280280
return false;
281281
}

clang/lib/AST/TextNodeDumper.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,8 @@ void TextNodeDumper::VisitVarDecl(const VarDecl *D) {
13841384
break;
13851385
}
13861386
}
1387+
if (D->needsDestruction(D->getASTContext()))
1388+
OS << " destroyed";
13871389
if (D->isParameterPack())
13881390
OS << " pack";
13891391
}

clang/lib/CodeGen/CGCall.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3093,7 +3093,7 @@ void CodeGenFunction::EmitDelegateCallArg(CallArgList &args,
30933093
// Deactivate the cleanup for the callee-destructed param that was pushed.
30943094
if (hasAggregateEvaluationKind(type) && !CurFuncIsThunk &&
30953095
type->getAs<RecordType>()->getDecl()->isParamDestroyedInCallee() &&
3096-
type.isDestructedType()) {
3096+
param->needsDestruction(getContext())) {
30973097
EHScopeStack::stable_iterator cleanup =
30983098
CalleeDestructedParamCleanups.lookup(cast<ParmVarDecl>(param));
30993099
assert(cleanup.isValid() &&

clang/lib/CodeGen/CGClass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2083,7 +2083,7 @@ static bool canEmitDelegateCallArgs(CodeGenFunction &CGF,
20832083
if (CGF.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
20842084
// If the parameters are callee-cleanup, it's not safe to forward.
20852085
for (auto *P : Ctor->parameters())
2086-
if (P->getType().isDestructedType())
2086+
if (P->needsDestruction(CGF.getContext()))
20872087
return false;
20882088

20892089
// Likewise if they're inalloca.

0 commit comments

Comments
 (0)