Skip to content

[Clang] Implement P2718R0 "Lifetime extension in range-based for loops" #76361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ C++20 Feature Support
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^

- Implemented `P2718R0: Lifetime extension in range-based for loops <https://wg21.link/P2718R0>`_. Also
materialize temporary object which is a prvalue in discarded-value expression.

C++2c Feature Support
^^^^^^^^^^^^^^^^^^^^^

Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -2402,7 +2402,7 @@ class Parser : public CodeCompletionHandler {
struct ForRangeInit {
SourceLocation ColonLoc;
ExprResult RangeExpr;

SmallVector<MaterializeTemporaryExpr *, 8> LifetimeExtendTemps;
bool ParsedForRangeDecl() { return !ColonLoc.isInvalid(); }
};
struct ForRangeInfo : ForRangeInit {
Expand Down
104 changes: 88 additions & 16 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,12 @@ class Sema final {
/// context not already known to be immediately invoked.
llvm::SmallPtrSet<DeclRefExpr *, 4> ReferenceToConsteval;

/// P2718R0 - Lifetime extension in range-based for loops.
/// MaterializeTemporaryExprs in for-range-init expressions which need to
/// extend lifetime. Add MaterializeTemporaryExpr* if the value of
/// InLifetimeExtendingContext is true.
SmallVector<MaterializeTemporaryExpr *, 8> ForRangeLifetimeExtendTemps;

/// \brief Describes whether we are in an expression constext which we have
/// to handle differently.
enum ExpressionKind {
Expand All @@ -1361,6 +1367,39 @@ class Sema final {
// VLAs).
bool InConditionallyConstantEvaluateContext = false;

/// Whether we are currently in a context in which all temporaries must be
/// lifetime-extended, even if they're not bound to a reference (for
/// example, in a for-range initializer).
bool InLifetimeExtendingContext = false;

/// Whether we are currently in a context in which all temporaries must be
/// materialized.
///
/// [class.temporary]/p2:
/// The materialization of a temporary object is generally delayed as long
/// as possible in order to avoid creating unnecessary temporary objects.
///
/// Temporary objects are materialized:
/// (2.1) when binding a reference to a prvalue ([dcl.init.ref],
/// [expr.type.conv], [expr.dynamic.cast], [expr.static.cast],
/// [expr.const.cast], [expr.cast]),
///
/// (2.2) when performing member access on a class prvalue ([expr.ref],
/// [expr.mptr.oper]),
///
/// (2.3) when performing an array-to-pointer conversion or subscripting
/// on an array prvalue ([conv.array], [expr.sub]),
///
/// (2.4) when initializing an object of type
/// std​::​initializer_list<T> from a braced-init-list
/// ([dcl.init.list]),
///
/// (2.5) for certain unevaluated operands ([expr.typeid], [expr.sizeof])
///
/// (2.6) when a prvalue that has type other than cv void appears as a
/// discarded-value expression ([expr.context]).
bool InMaterializeTemporaryObjectContext = false;

// When evaluating immediate functions in the initializer of a default
// argument or default member initializer, this is the declaration whose
// default initializer is being evaluated and the location of the call
Expand Down Expand Up @@ -5253,22 +5292,17 @@ class Sema final {
BFRK_Check
};

StmtResult ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc,
SourceLocation CoawaitLoc,
Stmt *InitStmt,
Stmt *LoopVar,
SourceLocation ColonLoc, Expr *Collection,
SourceLocation RParenLoc,
BuildForRangeKind Kind);
StmtResult BuildCXXForRangeStmt(SourceLocation ForLoc,
SourceLocation CoawaitLoc,
Stmt *InitStmt,
SourceLocation ColonLoc,
Stmt *RangeDecl, Stmt *Begin, Stmt *End,
Expr *Cond, Expr *Inc,
Stmt *LoopVarDecl,
SourceLocation RParenLoc,
BuildForRangeKind Kind);
StmtResult ActOnCXXForRangeStmt(
Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc,
Stmt *InitStmt, Stmt *LoopVar, SourceLocation ColonLoc, Expr *Collection,
SourceLocation RParenLoc, BuildForRangeKind Kind,
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
StmtResult BuildCXXForRangeStmt(
SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt,
SourceLocation ColonLoc, Stmt *RangeDecl, Stmt *Begin, Stmt *End,
Expr *Cond, Expr *Inc, Stmt *LoopVarDecl, SourceLocation RParenLoc,
BuildForRangeKind Kind,
ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {});
StmtResult FinishCXXForRangeStmt(Stmt *ForRange, Stmt *Body);

StmtResult ActOnGotoStmt(SourceLocation GotoLoc,
Expand Down Expand Up @@ -10010,6 +10044,18 @@ class Sema final {
return currentEvaluationContext().isImmediateFunctionContext();
}

bool isInLifetimeExtendingContext() const {
assert(!ExprEvalContexts.empty() &&
"Must be in an expression evaluation context");
return ExprEvalContexts.back().InLifetimeExtendingContext;
}

bool isInMaterializeTemporaryObjectContext() const {
assert(!ExprEvalContexts.empty() &&
"Must be in an expression evaluation context");
return ExprEvalContexts.back().InMaterializeTemporaryObjectContext;
}

bool isCheckingDefaultArgumentOrInitializer() const {
const ExpressionEvaluationContextRecord &Ctx = currentEvaluationContext();
return (Ctx.Context ==
Expand Down Expand Up @@ -10049,6 +10095,32 @@ class Sema final {
return Res;
}

/// keepInLifetimeExtendingContext - Pull down InLifetimeExtendingContext
/// flag from previous context.
void keepInLifetimeExtendingContext() {
if (ExprEvalContexts.size() > 2 &&
ExprEvalContexts[ExprEvalContexts.size() - 2]
.InLifetimeExtendingContext) {
auto &LastRecord = ExprEvalContexts.back();
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
LastRecord.InLifetimeExtendingContext =
PrevRecord.InLifetimeExtendingContext;
}
}

/// keepInMaterializeTemporaryObjectContext - Pull down
/// InMaterializeTemporaryObjectContext flag from previous context.
void keepInMaterializeTemporaryObjectContext() {
if (ExprEvalContexts.size() > 2 &&
ExprEvalContexts[ExprEvalContexts.size() - 2]
.InMaterializeTemporaryObjectContext) {
auto &LastRecord = ExprEvalContexts.back();
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
LastRecord.InMaterializeTemporaryObjectContext =
PrevRecord.InMaterializeTemporaryObjectContext;
}
}

/// RAII class used to determine whether SFINAE has
/// trapped any errors that occur during template argument
/// deduction.
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Frontend/InitPreprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,9 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
: "200704");
Builder.defineMacro("__cpp_constexpr_in_decltype", "201711L");
Builder.defineMacro("__cpp_range_based_for",
LangOpts.CPlusPlus17 ? "201603L" : "200907");
LangOpts.CPlusPlus23 ? "202211L"
: LangOpts.CPlusPlus17 ? "201603L"
: "200907");
Builder.defineMacro("__cpp_static_assert", LangOpts.CPlusPlus26 ? "202306L"
: LangOpts.CPlusPlus17
? "201411L"
Expand Down
26 changes: 26 additions & 0 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2312,12 +2312,38 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS,
bool IsForRangeLoop = false;
if (TryConsumeToken(tok::colon, FRI->ColonLoc)) {
IsForRangeLoop = true;
EnterExpressionEvaluationContext ForRangeInitContext(
Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated,
/*LambdaContextDecl=*/nullptr,
Sema::ExpressionEvaluationContextRecord::EK_Other,
getLangOpts().CPlusPlus23);

// P2718R0 - Lifetime extension in range-based for loops.
if (getLangOpts().CPlusPlus23) {
auto &LastRecord = Actions.ExprEvalContexts.back();
LastRecord.InLifetimeExtendingContext = true;

// Materialize non-`cv void` prvalue temporaries in discarded
// expressions. These materialized temporaries may be lifetime-extented.
LastRecord.InMaterializeTemporaryObjectContext = true;
}

if (getLangOpts().OpenMP)
Actions.startOpenMPCXXRangeFor();
if (Tok.is(tok::l_brace))
FRI->RangeExpr = ParseBraceInitializer();
else
FRI->RangeExpr = ParseExpression();

// Before c++23, ForRangeLifetimeExtendTemps should be empty.
assert(
getLangOpts().CPlusPlus23 ||
Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps.empty());

// Move the collected materialized temporaries into ForRangeInit before
// ForRangeInitContext exit.
FRI->LifetimeExtendTemps = std::move(
Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps);
}

Decl *ThisDecl = Actions.ActOnDeclarator(getCurScope(), D);
Expand Down
8 changes: 4 additions & 4 deletions clang/lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2288,11 +2288,11 @@ StmtResult Parser::ParseForStatement(SourceLocation *TrailingElseLoc) {
ForRangeStmt = Actions.ActOnCXXForRangeStmt(
getCurScope(), ForLoc, CoawaitLoc, FirstPart.get(),
ForRangeInfo.LoopVar.get(), ForRangeInfo.ColonLoc, CorrectedRange.get(),
T.getCloseLocation(), Sema::BFRK_Build);

// Similarly, we need to do the semantic analysis for a for-range
// statement immediately in order to close over temporaries correctly.
T.getCloseLocation(), Sema::BFRK_Build,
ForRangeInfo.LifetimeExtendTemps);
} else if (ForEach) {
// Similarly, we need to do the semantic analysis for a for-range
// statement immediately in order to close over temporaries correctly.
ForEachStmt = Actions.ActOnObjCForCollectionStmt(ForLoc,
FirstPart.get(),
Collection.get(),
Expand Down
26 changes: 22 additions & 4 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6259,7 +6259,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
assert(Param->hasDefaultArg() && "can't build nonexistent default arg");

bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer();

bool InLifetimeExtendingContext = isInLifetimeExtendingContext();
std::optional<ExpressionEvaluationContextRecord::InitializationContext>
InitializationContext =
OutermostDeclarationWithDelayedImmediateInvocations();
Expand Down Expand Up @@ -6292,9 +6292,17 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc,
ImmediateCallVisitor V(getASTContext());
if (!NestedDefaultChecking)
V.TraverseDecl(Param);
if (V.HasImmediateCalls) {
ExprEvalContexts.back().DelayedDefaultInitializationContext = {
CallLoc, Param, CurContext};

// Rewrite the call argument that was created from the corresponding
// parameter's default argument.
if (V.HasImmediateCalls || InLifetimeExtendingContext) {
if (V.HasImmediateCalls)
ExprEvalContexts.back().DelayedDefaultInitializationContext = {
CallLoc, Param, CurContext};
// Pass down lifetime extending flag, and collect temporaries in
// CreateMaterializeTemporaryExpr when we rewrite the call argument.
keepInLifetimeExtendingContext();
keepInMaterializeTemporaryObjectContext();
EnsureImmediateInvocationInDefaultArgs Immediate(*this);
ExprResult Res;
runWithSufficientStackSpace(CallLoc, [&] {
Expand Down Expand Up @@ -18655,6 +18663,16 @@ void Sema::PopExpressionEvaluationContext() {
}
}

// Append the collected materialized temporaries into previous context before
// exit if the previous also is a lifetime extending context.
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
if (getLangOpts().CPlusPlus23 && isInLifetimeExtendingContext() &&
PrevRecord.InLifetimeExtendingContext && !ExprEvalContexts.empty()) {
auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2];
PrevRecord.ForRangeLifetimeExtendTemps.append(
Rec.ForRangeLifetimeExtendTemps);
}

WarnOnPendingNoDerefs(Rec);
HandleImmediateInvocations(*this, Rec);

Expand Down
46 changes: 26 additions & 20 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8222,21 +8222,6 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
E = result.get();
}

// C99 6.3.2.1:
// [Except in specific positions,] an lvalue that does not have
// array type is converted to the value stored in the
// designated object (and is no longer an lvalue).
if (E->isPRValue()) {
// In C, function designators (i.e. expressions of function type)
// are r-values, but we still want to do function-to-pointer decay
// on them. This is both technically correct and convenient for
// some clients.
if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType())
return DefaultFunctionArrayConversion(E);

return E;
}

if (getLangOpts().CPlusPlus) {
// The C++11 standard defines the notion of a discarded-value expression;
// normally, we don't need to do anything to handle it, but if it is a
Expand All @@ -8257,11 +8242,32 @@ ExprResult Sema::IgnoredValueConversions(Expr *E) {
// If the expression is a prvalue after this optional conversion, the
// temporary materialization conversion is applied.
//
// We skip this step: IR generation is able to synthesize the storage for
// itself in the aggregate case, and adding the extra node to the AST is
// just clutter.
// FIXME: We don't emit lifetime markers for the temporaries due to this.
// FIXME: Do any other AST consumers care about this?
// We do not materialize temporaries by default in order to avoid creating
// unnecessary temporary objects. If we skip this step, IR generation is
// able to synthesize the storage for itself in the aggregate case, and
// adding the extra node to the AST is just clutter.
if (isInMaterializeTemporaryObjectContext() && getLangOpts().CPlusPlus17 &&
E->isPRValue() && !E->getType()->isVoidType()) {
ExprResult Res = TemporaryMaterializationConversion(E);
if (Res.isInvalid())
return E;
E = Res.get();
}
return E;
}

// C99 6.3.2.1:
// [Except in specific positions,] an lvalue that does not have
// array type is converted to the value stored in the
// designated object (and is no longer an lvalue).
if (E->isPRValue()) {
// In C, function designators (i.e. expressions of function type)
// are r-values, but we still want to do function-to-pointer decay
// on them. This is both technically correct and convenient for
// some clients.
if (!getLangOpts().CPlusPlus && E->getType()->isFunctionType())
return DefaultFunctionArrayConversion(E);

return E;
}

Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8491,6 +8491,10 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary,
// are done in both CreateMaterializeTemporaryExpr and MaybeBindToTemporary,
// but there may be a chance to merge them.
Cleanup.setExprNeedsCleanups(false);
if (isInLifetimeExtendingContext()) {
auto &Record = ExprEvalContexts.back();
Record.ForRangeLifetimeExtendTemps.push_back(MTE);
}
return MTE;
}

Expand Down
Loading