llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: None (yronglin) <details> <summary>Changes</summary> Implement P2718R0 "Lifetime extension in range-based for loops" (https://wg21.link/P2718R0) Differential Revision: https://reviews.llvm.org/D153701 --- Patch is 43.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/76361.diff 10 Files Affected: - (modified) clang/include/clang/Parse/Parser.h (+1-1) - (modified) clang/include/clang/Sema/Sema.h (+33-1) - (modified) clang/lib/Parse/ParseDecl.cpp (+19) - (modified) clang/lib/Parse/ParseStmt.cpp (+1-1) - (modified) clang/lib/Sema/SemaExpr.cpp (+18-4) - (modified) clang/lib/Sema/SemaExprCXX.cpp (+26-20) - (modified) clang/lib/Sema/SemaInit.cpp (+4) - (modified) clang/lib/Sema/SemaStmt.cpp (+9-1) - (added) clang/test/AST/ast-dump-for-range-lifetime.cpp (+355) - (modified) clang/test/CXX/special/class.temporary/p6.cpp (+234) ``````````diff diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 2dbe090bd0932f..a467ff6157962c 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2377,7 +2377,7 @@ class Parser : public CodeCompletionHandler { struct ForRangeInit { SourceLocation ColonLoc; ExprResult RangeExpr; - + SmallVector<MaterializeTemporaryExpr *, 8> LifetimeExtendTemps; bool ParsedForRangeDecl() { return !ColonLoc.isInvalid(); } }; struct ForRangeInfo : ForRangeInit { diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 5e3b57ea33220b..250194f90d503c 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1338,6 +1338,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 expression which need to + /// extend lifetime. Add MaterializeTemporaryExpr* if the value of + /// IsInLifetimeExtendingContext is true. + SmallVector<MaterializeTemporaryExpr *, 8> ForRangeLifetimeExtendTemps; + /// \brief Describes whether we are in an expression constext which we have /// to handle differently. enum ExpressionKind { @@ -1357,6 +1363,19 @@ class Sema final { // VLAs). bool InConditionallyConstantEvaluateContext = false; + /// Whether we are currently in a context in which temporaries must be + /// lifetime-extended (Eg. in a for-range initializer). + bool IsInLifetimeExtendingContext = false; + + /// Whether we should materialize temporaries in discarded expressions. + /// + /// [C++23][class.temporary]/p2.6 when a prvalue that has type other than cv + /// void appears as a discarded-value expression ([expr.context]). + /// + /// We do not materialize temporaries by default in order to avoid creating + /// unnecessary temporary objects. + bool MaterializePRValueInDiscardedExpression = 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 @@ -5215,7 +5234,8 @@ class Sema final { Stmt *LoopVar, SourceLocation ColonLoc, Expr *Collection, SourceLocation RParenLoc, - BuildForRangeKind Kind); + BuildForRangeKind Kind, + ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps = {}); StmtResult BuildCXXForRangeStmt(SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt, @@ -9956,6 +9976,18 @@ class Sema final { return currentEvaluationContext().isImmediateFunctionContext(); } + bool isInLifetimeExtendingContext() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().IsInLifetimeExtendingContext; + } + + bool ShouldMaterializePRValueInDiscardedExpression() const { + assert(!ExprEvalContexts.empty() && + "Must be in an expression evaluation context"); + return ExprEvalContexts.back().MaterializePRValueInDiscardedExpression; + } + bool isCheckingDefaultArgumentOrInitializer() const { const ExpressionEvaluationContextRecord &Ctx = currentEvaluationContext(); return (Ctx.Context == diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index ed006f9d67de45..8b809aa9c3df26 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2312,12 +2312,31 @@ 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.IsInLifetimeExtendingContext = true; + + // Materialize non-`cv void` prvalue temporaries in discarded + // expressions. These materialized temporaries may be lifetime-extented. + LastRecord.MaterializePRValueInDiscardedExpression = true; + } + if (getLangOpts().OpenMP) Actions.startOpenMPCXXRangeFor(); if (Tok.is(tok::l_brace)) FRI->RangeExpr = ParseBraceInitializer(); else FRI->RangeExpr = ParseExpression(); + if (getLangOpts().CPlusPlus23) + FRI->LifetimeExtendTemps = std::move( + Actions.ExprEvalContexts.back().ForRangeLifetimeExtendTemps); } Decl *ThisDecl = Actions.ActOnDeclarator(getCurScope(), D); diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index d0ff33bd1379ab..de883eb99765c9 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -2288,7 +2288,7 @@ 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); + T.getCloseLocation(), Sema::BFRK_Build, ForRangeInfo.LifetimeExtendTemps); // Similarly, we need to do the semantic analysis for a for-range // statement immediately in order to close over temporaries correctly. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 960f513d1111b2..45f6f50e6f7385 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -6256,7 +6256,7 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, assert(Param->hasDefaultArg() && "can't build nonexistent default arg"); bool NestedDefaultChecking = isCheckingDefaultArgumentOrInitializer(); - + bool IsInLifetimeExtendingContext = isInLifetimeExtendingContext(); std::optional<ExpressionEvaluationContextRecord::InitializationContext> InitializationContext = OutermostDeclarationWithDelayedImmediateInvocations(); @@ -6289,9 +6289,19 @@ 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 || IsInLifetimeExtendingContext) { + if (V.HasImmediateCalls) + ExprEvalContexts.back().DelayedDefaultInitializationContext = { + CallLoc, Param, CurContext}; + // Pass down lifetime extendning flag, and collect temporaries in + // CreateMaterializeTemporaryExpr when we rewrite the call argument. + auto &LastRecord = ExprEvalContexts.back(); + auto &PrevRecord = ExprEvalContexts[ExprEvalContexts.size() - 2]; + LastRecord.IsInLifetimeExtendingContext = IsInLifetimeExtendingContext; + EnsureImmediateInvocationInDefaultArgs Immediate(*this); ExprResult Res; runWithSufficientStackSpace(CallLoc, [&] { @@ -6305,6 +6315,10 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, if (Res.isInvalid()) return ExprError(); Init = Res.get(); + + // Collect MaterializeTemporaryExprs in the rewrited CXXDefaultArgExpr. + PrevRecord.ForRangeLifetimeExtendTemps.append( + LastRecord.ForRangeLifetimeExtendTemps); } } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 4ae04358d5df7c..3590582fb5912e 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -8209,21 +8209,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 @@ -8244,11 +8229,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 (ShouldMaterializePRValueInDiscardedExpression() && 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; } diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 61d244f3bb9798..42e4d1eacce78d 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -8469,6 +8469,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; } diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp index f0b03db690843a..759453f319e2b5 100644 --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -2487,7 +2487,8 @@ StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc, SourceLocation CoawaitLoc, Stmt *InitStmt, Stmt *First, SourceLocation ColonLoc, Expr *Range, SourceLocation RParenLoc, - BuildForRangeKind Kind) { + BuildForRangeKind Kind, + ArrayRef<MaterializeTemporaryExpr *> LifetimeExtendTemps) { // FIXME: recover in order to allow the body to be parsed. if (!First) return StmtError(); @@ -2539,6 +2540,12 @@ StmtResult Sema::ActOnCXXForRangeStmt(Scope *S, SourceLocation ForLoc, return StmtError(); } + if (getLangOpts().CPlusPlus23) { + auto Entity = InitializedEntity::InitializeVariable(RangeVar); + for (auto *MTE : LifetimeExtendTemps) + MTE->setExtendingDecl(RangeVar, Entity.allocateManglingNumber()); + } + // Claim the type doesn't contain auto: we've already done the checking. DeclGroupPtrTy RangeGroup = BuildDeclaratorGroup(MutableArrayRef<Decl *>((Decl **)&RangeVar, 1)); @@ -2783,6 +2790,7 @@ StmtResult Sema::BuildCXXForRangeStmt(SourceLocation ForLoc, LoopVar->setType(SubstAutoTypeDependent(LoopVar->getType())); } } else if (!BeginDeclStmt.get()) { + SourceLocation RangeLoc = RangeVar->getLocation(); const QualType RangeVarNonRefType = RangeVarType.getNonReferenceType(); diff --git a/clang/test/AST/ast-dump-for-range-lifetime.cpp b/clang/test/AST/ast-dump-for-range-lifetime.cpp new file mode 100644 index 00000000000000..dec2e29184526e --- /dev/null +++ b/clang/test/AST/ast-dump-for-range-lifetime.cpp @@ -0,0 +1,355 @@ +// RUN: %clang_cc1 -std=c++23 -triple x86_64-linux-gnu -fcxx-exceptions -ast-dump %s \ +// RUN: | FileCheck -strict-whitespace %s + +namespace P2718R0 { + +// Test basic +struct A { + int a[3] = {1, 2, 3}; + A() {} + ~A() {} + const int *begin() const { return a; } + const int *end() const { return a + 3; } + A& r() { return *this; } + A g() { return A(); } +}; + +A g() { return A(); } +const A &f1(const A &t) { return t; } + +void test1() { + [[maybe_unused]] int sum = 0; + // CHECK: FunctionDecl {{.*}} test1 'void ()' + // CHECK: | `-CXXForRangeStmt {{.*}} + // CHECK-NEXT: | |-<<<NULL>>> + // CHECK-NEXT: | |-DeclStmt {{.*}} + // CHECK-NEXT: | | `-VarDecl {{.*}} implicit used __range1 'const A &' cinit + // CHECK-NEXT: | | `-ExprWithCleanups {{.*}} 'const A':'const P2718R0::A' lvalue + // CHECK-NEXT: | | `-CallExpr {{.*}} 'const A':'const P2718R0::A' lvalue + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'const A &(*)(const A &)' <FunctionToPointerDecay> + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const A &(const A &)' lvalue Function {{.*}} 'f1' 'const A &(const A &)' + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const A':'const P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'const A &' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const A':'const P2718R0::A' <NoOp> + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CallExpr {{.*}} 'A':'P2718R0::A' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'A (*)()' <FunctionToPointerDecay> + // CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'A ()' lvalue Function {{.*}} 'g' 'A ()' + for (auto e : f1(g())) + sum += e; +} + +struct B : A {}; +int (&f(const A *))[3]; +const A *g(const A &); +void bar(int) {} + +void test2() { + // CHECK: FunctionDecl {{.*}} test2 'void ()' + // CHECK: | `-CXXForRangeStmt {{.*}} + // CHECK-NEXT: | |-<<<NULL>>> + // CHECK-NEXT: | |-DeclStmt {{.*}} + // CHECK-NEXT: | | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | | `-CallExpr {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'int (&(*)(const A *))[3]' <FunctionToPointerDecay> + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'int (&(const A *))[3]' lvalue Function {{.*}} 'f' 'int (&(const A *))[3]' + // CHECK-NEXT: | | `-CallExpr {{.*}} 'const A *' + // CHECK-NEXT: | | |-ImplicitCastExpr {{.*}} 'const A *(*)(const A &)' <FunctionToPointerDecay> + // CHECK-NEXT: | | | `-DeclRefExpr {{.*}} 'const A *(const A &)' lvalue Function {{.*}} 'g' 'const A *(const A &)' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const A':'const P2718R0::A' lvalue <DerivedToBase (A)> + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'const B':'const P2718R0::B' lvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-ImplicitCastExpr {{.*}} 'const B':'const P2718R0::B' <NoOp> + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'B':'P2718R0::B' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'B':'P2718R0::B' 'void () noexcept(false)' zeroing + for (auto e : f(g(B()))) + bar(e); +} + +// Test discard statement. +struct LockGuard { + LockGuard() {} + ~LockGuard() {} +}; + +void test3() { + int v[] = {42, 17, 13}; + + // CHECK: FunctionDecl {{.*}} test3 'void ()' + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<<NULL>>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-CXXStaticCastExpr {{.*}} 'void' static_cast<void> <ToVoid> + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' xvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : static_cast<void>(LockGuard()), v) + LockGuard guard; + + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<<NULL>>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-CStyleCastExpr {{.*}} 'void' <ToVoid> + // CHECK-NEXT: | | `-MaterializeTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' xvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : (void)LockGuard(), v) + LockGuard guard; + + // CHECK: -CXXForRangeStmt {{.*}} + // CHECK-NEXT: |-<<<NULL>>> + // CHECK-NEXT: |-DeclStmt {{.*}} + // CHECK-NEXT: | `-VarDecl {{.*}} implicit used __range1 'int (&)[3]' cinit + // CHECK-NEXT: | `-ExprWithCleanups {{.*}} 'int[3]' lvalue + // CHECK-NEXT: | `-BinaryOperator {{.*}} 'int[3]' lvalue ',' + // CHECK-NEXT: | |-MaterializeTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' xvalue extended by Var {{.*}} '__range1' 'int (&)[3]' + // CHECK-NEXT: | | `-CXXBindTemporaryExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' (CXXTemporary {{.*}}) + // CHECK-NEXT: | | `-CXXTemporaryObjectExpr {{.*}} 'LockGuard':'P2718R0::LockGuard' 'void ()' + // CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int[3]' lvalue Var {{.*}} 'v' 'int[3]' + for ([[maybe_unused]] int x : LockGuard(), v) + LockGuard guard; +} + +// Test default arg +int (&default_arg_fn(const A & = A()))[3]; +void test4() { + + // CHECK: FunctionDecl {{.*}} test4 'void ()' + // FIXME: Should dump CXXDefaultArgExpr->getExpr() if CXXDefaultArgExpr has been rewrited? + for (auto e : default_arg_fn()) + bar(e); +} + +struct DefaultA { + DefaultA() {} + ~DefaultA() {} +}; ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/76361 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits