Skip to content

Commit e5f2bfb

Browse files
committed
[Clang] Extend lifetime of temporaries in mem-default-init for P2718R0
Signed-off-by: yronglin <[email protected]>
1 parent 935e699 commit e5f2bfb

File tree

15 files changed

+222
-117
lines changed

15 files changed

+222
-117
lines changed

clang/include/clang/Sema/Sema.h

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5078,34 +5078,6 @@ class Sema final : public SemaBase {
50785078
/// example, in a for-range initializer).
50795079
bool InLifetimeExtendingContext = false;
50805080

5081-
/// Whether we are currently in a context in which all temporaries must be
5082-
/// materialized.
5083-
///
5084-
/// [class.temporary]/p2:
5085-
/// The materialization of a temporary object is generally delayed as long
5086-
/// as possible in order to avoid creating unnecessary temporary objects.
5087-
///
5088-
/// Temporary objects are materialized:
5089-
/// (2.1) when binding a reference to a prvalue ([dcl.init.ref],
5090-
/// [expr.type.conv], [expr.dynamic.cast], [expr.static.cast],
5091-
/// [expr.const.cast], [expr.cast]),
5092-
///
5093-
/// (2.2) when performing member access on a class prvalue ([expr.ref],
5094-
/// [expr.mptr.oper]),
5095-
///
5096-
/// (2.3) when performing an array-to-pointer conversion or subscripting
5097-
/// on an array prvalue ([conv.array], [expr.sub]),
5098-
///
5099-
/// (2.4) when initializing an object of type
5100-
/// std​::​initializer_list<T> from a braced-init-list
5101-
/// ([dcl.init.list]),
5102-
///
5103-
/// (2.5) for certain unevaluated operands ([expr.typeid], [expr.sizeof])
5104-
///
5105-
/// (2.6) when a prvalue that has type other than cv void appears as a
5106-
/// discarded-value expression ([expr.context]).
5107-
bool InMaterializeTemporaryObjectContext = false;
5108-
51095081
// When evaluating immediate functions in the initializer of a default
51105082
// argument or default member initializer, this is the declaration whose
51115083
// default initializer is being evaluated and the location of the call
@@ -6386,19 +6358,6 @@ class Sema final : public SemaBase {
63866358
}
63876359
}
63886360

6389-
/// keepInMaterializeTemporaryObjectContext - Pull down
6390-
/// InMaterializeTemporaryObjectContext flag from previous context.
6391-
void keepInMaterializeTemporaryObjectContext() {
6392-
if (ExprEvalContexts.size() > 2 &&
6393-
ExprEvalContexts[ExprEvalContexts.size() - 2]
6394-
.InMaterializeTemporaryObjectContext) {
6395-
auto &LastRecord = ExprEvalContexts.back();
6396-
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
6397-
LastRecord.InMaterializeTemporaryObjectContext =
6398-
PrevRecord.InMaterializeTemporaryObjectContext;
6399-
}
6400-
}
6401-
64026361
DefaultedComparisonKind getDefaultedComparisonKind(const FunctionDecl *FD) {
64036362
return getDefaultedFunctionKind(FD).asComparison();
64046363
}
@@ -6542,12 +6501,6 @@ class Sema final : public SemaBase {
65426501
/// used in initializer of the field.
65436502
llvm::MapVector<FieldDecl *, DeleteLocs> DeleteExprs;
65446503

6545-
bool isInMaterializeTemporaryObjectContext() const {
6546-
assert(!ExprEvalContexts.empty() &&
6547-
"Must be in an expression evaluation context");
6548-
return ExprEvalContexts.back().InMaterializeTemporaryObjectContext;
6549-
}
6550-
65516504
ParsedType getInheritingConstructorName(CXXScopeSpec &SS,
65526505
SourceLocation NameLoc,
65536506
IdentifierInfo &Name);

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,9 @@ void CodeGenFunction::EmitAnyExprToMem(const Expr *E,
274274
llvm_unreachable("bad evaluation kind");
275275
}
276276

277-
static void
278-
pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
279-
const Expr *E, Address ReferenceTemporary) {
277+
void CodeGenFunction::pushTemporaryCleanup(const MaterializeTemporaryExpr *M,
278+
const Expr *E,
279+
Address ReferenceTemporary) {
280280
// Objective-C++ ARC:
281281
// If we are binding a reference to a temporary that has ownership, we
282282
// need to perform retain/release operations on the temporary.
@@ -311,9 +311,9 @@ pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
311311
CleanupKind CleanupKind;
312312
if (Lifetime == Qualifiers::OCL_Strong) {
313313
const ValueDecl *VD = M->getExtendingDecl();
314-
bool Precise =
315-
VD && isa<VarDecl>(VD) && VD->hasAttr<ObjCPreciseLifetimeAttr>();
316-
CleanupKind = CGF.getARCCleanupKind();
314+
bool Precise = isa_and_nonnull<VarDecl>(VD) &&
315+
VD->hasAttr<ObjCPreciseLifetimeAttr>();
316+
CleanupKind = getARCCleanupKind();
317317
Destroy = Precise ? &CodeGenFunction::destroyARCStrongPrecise
318318
: &CodeGenFunction::destroyARCStrongImprecise;
319319
} else {
@@ -323,13 +323,12 @@ pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
323323
Destroy = &CodeGenFunction::destroyARCWeak;
324324
}
325325
if (Duration == SD_FullExpression)
326-
CGF.pushDestroy(CleanupKind, ReferenceTemporary,
327-
M->getType(), *Destroy,
328-
CleanupKind & EHCleanup);
326+
pushDestroy(CleanupKind, ReferenceTemporary, M->getType(), *Destroy,
327+
CleanupKind & EHCleanup);
329328
else
330-
CGF.pushLifetimeExtendedDestroy(CleanupKind, ReferenceTemporary,
331-
M->getType(),
332-
*Destroy, CleanupKind & EHCleanup);
329+
pushLifetimeExtendedDestroy(CleanupKind, ReferenceTemporary,
330+
M->getType(), *Destroy,
331+
CleanupKind & EHCleanup);
333332
return;
334333

335334
case SD_Dynamic:
@@ -358,32 +357,31 @@ pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
358357
llvm::FunctionCallee CleanupFn;
359358
llvm::Constant *CleanupArg;
360359
if (E->getType()->isArrayType()) {
361-
CleanupFn = CodeGenFunction(CGF.CGM).generateDestroyHelper(
362-
ReferenceTemporary, E->getType(),
363-
CodeGenFunction::destroyCXXObject, CGF.getLangOpts().Exceptions,
360+
CleanupFn = CodeGenFunction(CGM).generateDestroyHelper(
361+
ReferenceTemporary, E->getType(), CodeGenFunction::destroyCXXObject,
362+
getLangOpts().Exceptions,
364363
dyn_cast_or_null<VarDecl>(M->getExtendingDecl()));
365-
CleanupArg = llvm::Constant::getNullValue(CGF.Int8PtrTy);
364+
CleanupArg = llvm::Constant::getNullValue(Int8PtrTy);
366365
} else {
367-
CleanupFn = CGF.CGM.getAddrAndTypeOfCXXStructor(
366+
CleanupFn = CGM.getAddrAndTypeOfCXXStructor(
368367
GlobalDecl(ReferenceTemporaryDtor, Dtor_Complete));
369-
CleanupArg = cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(CGF));
368+
CleanupArg =
369+
cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(*this));
370370
}
371-
CGF.CGM.getCXXABI().registerGlobalDtor(
372-
CGF, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
371+
CGM.getCXXABI().registerGlobalDtor(
372+
*this, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
373373
break;
374374
}
375375

376376
case SD_FullExpression:
377-
CGF.pushDestroy(NormalAndEHCleanup, ReferenceTemporary, E->getType(),
378-
CodeGenFunction::destroyCXXObject,
379-
CGF.getLangOpts().Exceptions);
377+
pushDestroy(NormalAndEHCleanup, ReferenceTemporary, E->getType(),
378+
CodeGenFunction::destroyCXXObject, getLangOpts().Exceptions);
380379
break;
381380

382381
case SD_Automatic:
383-
CGF.pushLifetimeExtendedDestroy(NormalAndEHCleanup,
384-
ReferenceTemporary, E->getType(),
385-
CodeGenFunction::destroyCXXObject,
386-
CGF.getLangOpts().Exceptions);
382+
pushLifetimeExtendedDestroy(NormalAndEHCleanup, ReferenceTemporary,
383+
E->getType(), CodeGenFunction::destroyCXXObject,
384+
getLangOpts().Exceptions);
387385
break;
388386

389387
case SD_Dynamic:
@@ -490,7 +488,7 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
490488
}
491489
}
492490

493-
pushTemporaryCleanup(*this, M, E, Object);
491+
pushTemporaryCleanup(M, E, Object);
494492
return RefTempDst;
495493
}
496494

@@ -579,7 +577,13 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
579577
}
580578
EmitAnyExprToMem(E, Object, Qualifiers(), /*IsInit*/true);
581579
}
582-
pushTemporaryCleanup(*this, M, E, Object);
580+
581+
// If this temporary extended by for-range variable, delay to emitting
582+
// cleanup.
583+
if (CurLexicalScope && CurLexicalScope->isExtendedByForRangeVar(M))
584+
CurLexicalScope->addForRangeInitTemp(M, Object);
585+
else
586+
pushTemporaryCleanup(M, E, Object);
583587

584588
// Perform derived-to-base casts and/or field accesses, to get from the
585589
// temporary object we created (and, potentially, for which we extended

clang/lib/CodeGen/CGStmt.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,11 +1230,26 @@ CodeGenFunction::EmitCXXForRangeStmt(const CXXForRangeStmt &S,
12301230
JumpDest LoopExit = getJumpDestInCurrentScope("for.end");
12311231

12321232
LexicalScope ForScope(*this, S.getSourceRange());
1233+
const DeclStmt *RangeDS = cast<DeclStmt>(S.getRangeStmt());
1234+
const VarDecl *RangeVar = cast<VarDecl>(RangeDS->getSingleDecl());
1235+
if (getLangOpts().CPlusPlus23)
1236+
ForScope.setForRangeVar(RangeVar);
12331237

12341238
// Evaluate the first pieces before the loop.
12351239
if (S.getInit())
12361240
EmitStmt(S.getInit());
12371241
EmitStmt(S.getRangeStmt());
1242+
1243+
// Emit cleanup for tempories in for-range-init expression.
1244+
if (getLangOpts().CPlusPlus23) {
1245+
RunCleanupsScope Scope(*this);
1246+
auto LifetimeExtendTemps = ForScope.getForRangeInitTemps();
1247+
for (const auto &Temp : LifetimeExtendTemps) {
1248+
auto [M, Object] = Temp;
1249+
pushTemporaryCleanup(M, M->getSubExpr(), Object);
1250+
}
1251+
}
1252+
12381253
EmitStmt(S.getBeginStmt());
12391254
EmitStmt(S.getEndStmt());
12401255

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,9 @@ class CodeGenFunction : public CodeGenTypeCache {
875875
new (Buffer + sizeof(Header) + sizeof(T)) RawAddress(ActiveFlag);
876876
}
877877

878+
void pushTemporaryCleanup(const MaterializeTemporaryExpr *M, const Expr *E,
879+
Address ReferenceTemporary);
880+
878881
/// Set up the last cleanup that was pushed as a conditional
879882
/// full-expression cleanup.
880883
void initFullExprCleanup() {
@@ -982,11 +985,24 @@ class CodeGenFunction : public CodeGenTypeCache {
982985
EHScopeStack::stable_iterator CurrentCleanupScopeDepth =
983986
EHScopeStack::stable_end();
984987

988+
struct ForRangeInitLifetimeExtendTemporary {
989+
const MaterializeTemporaryExpr *M;
990+
RawAddress Object;
991+
};
992+
985993
class LexicalScope : public RunCleanupsScope {
986994
SourceRange Range;
987995
SmallVector<const LabelDecl*, 4> Labels;
996+
SmallVector<ForRangeInitLifetimeExtendTemporary, 4> ForRangeInitTemps;
988997
LexicalScope *ParentScope;
989998

999+
// This will be set to `__range` variable when we emitting a
1000+
// CXXForRangeStmt. It was used to check whether we are emitting a
1001+
// materialized temporary which in for-range-init and lifetime-extended by
1002+
// __range var. If so, the codegen of cleanup for that temporary object
1003+
// needs to be delayed.
1004+
const VarDecl *ForRangeVar = nullptr;
1005+
9901006
LexicalScope(const LexicalScope &) = delete;
9911007
void operator=(const LexicalScope &) = delete;
9921008

@@ -1004,6 +1020,21 @@ class CodeGenFunction : public CodeGenTypeCache {
10041020
Labels.push_back(label);
10051021
}
10061022

1023+
void addForRangeInitTemp(const MaterializeTemporaryExpr *M,
1024+
RawAddress Object) {
1025+
assert(PerformCleanup && "adding temps to dead scope?");
1026+
ForRangeInitTemps.push_back({M, Object});
1027+
}
1028+
1029+
ArrayRef<ForRangeInitLifetimeExtendTemporary> getForRangeInitTemps() const {
1030+
return ForRangeInitTemps;
1031+
}
1032+
1033+
void setForRangeVar(const VarDecl *Var) { ForRangeVar = Var; }
1034+
bool isExtendedByForRangeVar(const MaterializeTemporaryExpr *M) const {
1035+
return M && ForRangeVar && M->getExtendingDecl() == ForRangeVar;
1036+
}
1037+
10071038
/// Exit this cleanup scope, emitting any accumulated
10081039
/// cleanups.
10091040
~LexicalScope() {

clang/lib/Parse/ParseDecl.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2379,10 +2379,6 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS,
23792379
if (getLangOpts().CPlusPlus23) {
23802380
auto &LastRecord = Actions.ExprEvalContexts.back();
23812381
LastRecord.InLifetimeExtendingContext = true;
2382-
2383-
// Materialize non-`cv void` prvalue temporaries in discarded
2384-
// expressions. These materialized temporaries may be lifetime-extented.
2385-
LastRecord.InMaterializeTemporaryObjectContext = true;
23862382
}
23872383

23882384
if (getLangOpts().OpenMP)

clang/lib/Sema/SemaExpr.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6331,7 +6331,6 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
63316331
// Pass down lifetime extending flag, and collect temporaries in
63326332
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
63336333
keepInLifetimeExtendingContext();
6334-
keepInMaterializeTemporaryObjectContext();
63356334
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
63366335
ExprResult Res;
63376336
runWithSufficientStackSpace(CallLoc, [&] {
@@ -6377,7 +6376,7 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
63776376
Expr *Init = nullptr;
63786377

63796378
bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();
6380-
6379+
bool InLifetimeExtendingContext = isInLifetimeExtendingContext();
63816380
EnterExpressionEvaluationContext EvalContext(
63826381
*this, ExpressionEvaluationContext::PotentiallyEvaluated, Field);
63836382

@@ -6412,19 +6411,21 @@ ExprResult Sema::BuildCXXDefaultInitExpr(SourceLocation Loc, FieldDecl *Field) {
64126411
ImmediateCallVisitor V(getASTContext());
64136412
if (!NestedDefaultChecking)
64146413
V.TraverseDecl(Field);
6415-
if (V.HasImmediateCalls) {
6414+
if (V.HasImmediateCalls || InLifetimeExtendingContext) {
64166415
ExprEvalContexts.back().DelayedDefaultInitializationContext = {Loc, Field,
64176416
CurContext};
64186417
ExprEvalContexts.back().IsCurrentlyCheckingDefaultArgumentOrInitializer =
64196418
NestedDefaultChecking;
6420-
6419+
// Pass down lifetime extending flag, and collect temporaries in
6420+
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
6421+
keepInLifetimeExtendingContext();
64216422
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
64226423
ExprResult Res;
64236424
runWithSufficientStackSpace(Loc, [&] {
64246425
Res = Immediate.TransformInitializer(Field->getInClassInitializer(),
64256426
/*CXXDirectInit=*/false);
64266427
});
6427-
if (!Res.isInvalid())
6428+
if (Res.isUsable())
64286429
Res = ConvertMemberDefaultInitExpression(Field, Res.get(), Loc);
64296430
if (Res.isInvalid()) {
64306431
Field->setInvalidDecl();

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8361,7 +8361,7 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
83618361
// unnecessary temporary objects. If we skip this step, IR generation is
83628362
// able to synthesize the storage for itself in the aggregate case, and
83638363
// adding the extra node to the AST is just clutter.
8364-
if (isInMaterializeTemporaryObjectContext() && getLangOpts().CPlusPlus17 &&
8364+
if (isInLifetimeExtendingContext() && getLangOpts().CPlusPlus17 &&
83658365
E->isPRValue() && !E->getType()->isVoidType()) {
83668366
ExprResult Res = TemporaryMaterializationConversion(E);
83678367
if (Res.isInvalid())

clang/lib/Sema/SemaInit.cpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,26 @@ void InitListChecker::FillInEmptyInitForField(unsigned Init, FieldDecl *Field,
710710
if (VerifyOnly)
711711
return;
712712

713+
// Enter a lifetime extension context, then we can support lifetime
714+
// extension of temporary created by aggregate initialization using a
715+
// default member initializer (DR1815 https://wg21.link/CWG1815).
716+
//
717+
// In a lifetime extension context, BuildCXXDefaultInitExpr will clone the
718+
// initializer expression on each use that would lifetime extend its
719+
// temporaries.
720+
EnterExpressionEvaluationContext LifetimeExtensionContext(
721+
SemaRef, Sema::ExpressionEvaluationContext::PotentiallyEvaluated,
722+
/*LambdaContextDecl=*/nullptr,
723+
Sema::ExpressionEvaluationContextRecord::EK_Other, true);
724+
725+
// Lifetime extension in default-member-init.
726+
auto &LastRecord = SemaRef.ExprEvalContexts.back();
727+
728+
// Just copy previous record, make sure we haven't forget anything.
729+
LastRecord =
730+
SemaRef.ExprEvalContexts[SemaRef.ExprEvalContexts.size() - 2];
731+
LastRecord.InLifetimeExtendingContext = true;
732+
713733
ExprResult DIE = SemaRef.BuildCXXDefaultInitExpr(Loc, Field);
714734
if (DIE.isInvalid()) {
715735
hadError = true;
@@ -7699,6 +7719,8 @@ static void visitLocalsRetainedByReferenceBinding(IndirectLocalPath &Path,
76997719
// Step into CXXDefaultInitExprs so we can diagnose cases where a
77007720
// constructor inherits one as an implicit mem-initializer.
77017721
if (auto *DIE = dyn_cast<CXXDefaultInitExpr>(Init)) {
7722+
assert(DIE->hasRewrittenInit() &&
7723+
"CXXDefaultInitExpr must has rewritten init");
77027724
Path.push_back(
77037725
{IndirectLocalPathEntry::DefaultInit, DIE, DIE->getField()});
77047726
Init = DIE->getExpr();
@@ -8194,24 +8216,12 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
81948216

81958217
switch (shouldLifetimeExtendThroughPath(Path)) {
81968218
case PathLifetimeKind::Extend:
8219+
case PathLifetimeKind::ShouldExtend:
81978220
// Update the storage duration of the materialized temporary.
8198-
// FIXME: Rebuild the expression instead of mutating it.
81998221
MTE->setExtendingDecl(ExtendingEntity->getDecl(),
82008222
ExtendingEntity->allocateManglingNumber());
8201-
// Also visit the temporaries lifetime-extended by this initializer.
82028223
return true;
82038224

8204-
case PathLifetimeKind::ShouldExtend:
8205-
// We're supposed to lifetime-extend the temporary along this path (per
8206-
// the resolution of DR1815), but we don't support that yet.
8207-
//
8208-
// FIXME: Properly handle this situation. Perhaps the easiest approach
8209-
// would be to clone the initializer expression on each use that would
8210-
// lifetime extend its temporaries.
8211-
Diag(DiagLoc, diag::warn_unsupported_lifetime_extension)
8212-
<< RK << DiagRange;
8213-
break;
8214-
82158225
case PathLifetimeKind::NoExtend:
82168226
// If the path goes through the initialization of a variable or field,
82178227
// it can't possibly reach a temporary created in this full-expression.

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5477,7 +5477,6 @@ void Sema::InstantiateVariableInitializer(
54775477
*this, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, Var);
54785478

54795479
keepInLifetimeExtendingContext();
5480-
keepInMaterializeTemporaryObjectContext();
54815480
// Instantiate the initializer.
54825481
ExprResult Init;
54835482

0 commit comments

Comments
 (0)