Skip to content

Commit

Permalink
[Clang] Implement P2718R0 "Lifetime extension in range-based for loop…
Browse files Browse the repository at this point in the history
…s" (#76361)

Implement P2718R0 "Lifetime extension in range-based for loops"
(https://wg21.link/P2718R0)

Differential Revision: https://reviews.llvm.org/D153701

---------

Signed-off-by: yronglin <yronglin777@gmail.com>
  • Loading branch information
yronglin committed Jan 29, 2024
1 parent 37180ed commit 0aff71c
Show file tree
Hide file tree
Showing 16 changed files with 907 additions and 85 deletions.
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

0 comments on commit 0aff71c

Please sign in to comment.