[clang] [Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime (PR #91895)

Mital Ashok via cfe-commits cfe-commits at lists.llvm.org
Mon Aug 12 13:48:05 PDT 2024


https://github.com/MitalAshok updated https://github.com/llvm/llvm-project/pull/91895

>From 56aed689dc5825fc5bacc6dfdff58ee0eaf71f82 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Sun, 12 May 2024 19:48:24 +0100
Subject: [PATCH 01/11] [Clang] Add attribute for consteval builtins; Declare
 constexpr builtins as constexpr in C++

Also support redeclaring now-constexpr builtins without constexpr
---
 clang/include/clang/Basic/Builtins.h      |  5 +++++
 clang/include/clang/Basic/BuiltinsBase.td |  2 ++
 clang/lib/Sema/SemaDecl.cpp               | 15 +++++++++++----
 clang/lib/Sema/SemaDeclCXX.cpp            | 18 +++++++++++++-----
 clang/lib/Sema/SemaExpr.cpp               |  8 ++++++--
 clang/test/Sema/builtin-redecl.cpp        | 15 ++++++++++-----
 6 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/clang/include/clang/Basic/Builtins.h b/clang/include/clang/Basic/Builtins.h
index f955d21169556a..e85ec5b2dca14e 100644
--- a/clang/include/clang/Basic/Builtins.h
+++ b/clang/include/clang/Basic/Builtins.h
@@ -280,6 +280,11 @@ class Context {
     return strchr(getRecord(ID).Attributes, 'E') != nullptr;
   }
 
+  /// Returns true if this is an immediate (consteval) function
+  bool isImmediate(unsigned ID) const {
+    return strchr(getRecord(ID).Attributes, 'G') != nullptr;
+  }
+
 private:
   const Info &getRecord(unsigned ID) const;
 
diff --git a/clang/include/clang/Basic/BuiltinsBase.td b/clang/include/clang/Basic/BuiltinsBase.td
index 724747ec76d732..1196b9e15c10dc 100644
--- a/clang/include/clang/Basic/BuiltinsBase.td
+++ b/clang/include/clang/Basic/BuiltinsBase.td
@@ -70,6 +70,8 @@ class VScanfFormat<int I> : IndexedAttribute<"S", I>;
 
 // Builtin can be constant evaluated
 def Constexpr : Attribute<"E">;
+// Builtin is immediate and must be constant evaluated. Implies Constexpr.
+def Consteval : Attribute<"EG">;
 
 // Builtin kinds
 // =============
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fb913034bd8360..6b0a04585928a5 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2409,10 +2409,17 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
     Parent = CLinkageDecl;
   }
 
-  FunctionDecl *New = FunctionDecl::Create(Context, Parent, Loc, Loc, II, Type,
-                                           /*TInfo=*/nullptr, SC_Extern,
-                                           getCurFPFeatures().isFPConstrained(),
-                                           false, Type->isFunctionProtoType());
+  ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
+  if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isConstantEvaluated(ID)) {
+    ConstexprKind = ConstexprSpecKind::Constexpr;
+    if (Context.BuiltinInfo.isImmediate(ID))
+      ConstexprKind = ConstexprSpecKind::Consteval;
+  }
+
+  FunctionDecl *New = FunctionDecl::Create(
+      Context, Parent, Loc, Loc, II, Type, /*TInfo=*/nullptr, SC_Extern,
+      getCurFPFeatures().isFPConstrained(), /*isInlineSpecified=*/false,
+      Type->isFunctionProtoType(), ConstexprKind);
   New->setImplicit();
   New->addAttr(BuiltinAttr::CreateImplicit(Context, ID));
 
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 53238d355ea091..1b558d70f9b487 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -676,11 +676,19 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old,
   // template has a constexpr specifier then all its declarations shall
   // contain the constexpr specifier.
   if (New->getConstexprKind() != Old->getConstexprKind()) {
-    Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
-        << New << static_cast<int>(New->getConstexprKind())
-        << static_cast<int>(Old->getConstexprKind());
-    Diag(Old->getLocation(), diag::note_previous_declaration);
-    Invalid = true;
+    if (Old->getBuiltinID() &&
+        Old->getConstexprKind() == ConstexprSpecKind::Constexpr &&
+        New->getConstexprKind() == ConstexprSpecKind::Unspecified) {
+      // Except allow redeclaring a builtin as non-constexpr to match C
+      // redeclarations which will not be constexpr
+      New->setConstexprKind(ConstexprSpecKind::Constexpr);
+    } else {
+      Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
+          << New << static_cast<int>(New->getConstexprKind())
+          << static_cast<int>(Old->getConstexprKind());
+      Diag(Old->getLocation(), diag::note_previous_declaration);
+      Invalid = true;
+    }
   } else if (!Old->getMostRecentDecl()->isInlined() && New->isInlined() &&
              Old->isDefined(Def) &&
              // If a friend function is inlined but does not have 'inline'
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index bb4b116fd73ca6..39aa32526d2b1c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7095,8 +7095,12 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
   }
 
   // Bail out early if calling a builtin with custom type checking.
-  if (BuiltinID && Context.BuiltinInfo.hasCustomTypechecking(BuiltinID))
-    return CheckBuiltinFunctionCall(FDecl, BuiltinID, TheCall);
+  if (BuiltinID && Context.BuiltinInfo.hasCustomTypechecking(BuiltinID)) {
+    ExprResult E = CheckBuiltinFunctionCall(FDecl, BuiltinID, TheCall);
+    if (!E.isInvalid() && Context.BuiltinInfo.isImmediate(BuiltinID))
+      E = CheckForImmediateInvocation(E, FDecl);
+    return E;
+  }
 
   if (getLangOpts().CUDA) {
     if (Config) {
diff --git a/clang/test/Sema/builtin-redecl.cpp b/clang/test/Sema/builtin-redecl.cpp
index 323c63e2028833..31409a4d46a65b 100644
--- a/clang/test/Sema/builtin-redecl.cpp
+++ b/clang/test/Sema/builtin-redecl.cpp
@@ -14,13 +14,18 @@ void __builtin_va_copy(double d);
 // expected-error at +2 {{cannot redeclare builtin function '__builtin_va_end'}}
 // expected-note at +1 {{'__builtin_va_end' is a builtin with type}}
 void __builtin_va_end(__builtin_va_list);
-// RUN: %clang_cc1 %s -fsyntax-only -verify 
-// RUN: %clang_cc1 %s -fsyntax-only -verify -x c
 
 void __va_start(__builtin_va_list*, ...);
 
+          void *__builtin_assume_aligned(const void *, size_t, ...);
 #ifdef __cplusplus
-void *__builtin_assume_aligned(const void *, size_t, ...) noexcept;
-#else
-void *__builtin_assume_aligned(const void *, size_t, ...);
+constexpr void *__builtin_assume_aligned(const void *, size_t, ...);
+          void *__builtin_assume_aligned(const void *, size_t, ...) noexcept;
+constexpr void *__builtin_assume_aligned(const void *, size_t, ...) noexcept;
+          void *__builtin_assume_aligned(const void *, size_t, ...) throw();
+constexpr void *__builtin_assume_aligned(const void *, size_t, ...) throw();
+
+// expected-error at +1 {{constexpr declaration of '__builtin_calloc' follows non-constexpr declaration}}
+constexpr void *__builtin_calloc(size_t, size_t);
+// expected-note at -1 {{previous declaration is here}}
 #endif

>From 253f58f0e34bc6dedbbfe17f68cfe9baa3a9146f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Sun, 12 May 2024 18:42:49 +0100
Subject: [PATCH 02/11] [Clang] Add __builtin_is_within_lifetime to implement
 P2641R4's std::is_within_lifetime

---
 clang/include/clang/Basic/Builtins.td         |   6 +
 .../include/clang/Basic/DiagnosticASTKinds.td |  28 ++-
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/lib/AST/ExprConstant.cpp                |  89 +++++++-
 clang/lib/AST/Interp/State.h                  |   1 +
 clang/lib/Sema/SemaChecking.cpp               |  26 +++
 .../SemaCXX/builtin-is-within-lifetime.cpp    | 211 ++++++++++++++++++
 7 files changed, 345 insertions(+), 19 deletions(-)
 create mode 100644 clang/test/SemaCXX/builtin-is-within-lifetime.cpp

diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 11982af3fa609b..04660122565b86 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -932,6 +932,12 @@ def IsConstantEvaluated : LangBuiltin<"CXX_LANG"> {
   let Prototype = "bool()";
 }
 
+def IsWithinLifetime : LangBuiltin<"CXX_LANG"> {
+  let Spellings = ["__builtin_is_within_lifetime"];
+  let Attributes = [NoThrow, CustomTypeChecking, Consteval];
+  let Prototype = "bool(void*)";
+}
+
 // GCC exception builtins
 def EHReturn : Builtin {
   let Spellings = ["__builtin_eh_return"];
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index a024f9b2a9f8c0..7f8908e45663b9 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -169,12 +169,12 @@ def note_constexpr_this : Note<
 def note_constexpr_lifetime_ended : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of}0 %select{temporary|variable}1 whose "
+  "destruction of|read of}0 %select{temporary|variable}1 whose "
   "%plural{8:storage duration|:lifetime}0 has ended">;
 def note_constexpr_access_uninit : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|"
-  "construction of subobject of|destruction of}0 "
+  "construction of subobject of|destruction of|read of}0 "
   "%select{object outside its lifetime|uninitialized object}1 "
   "is not allowed in a constant expression">;
 def note_constexpr_use_uninit_reference : Note<
@@ -185,11 +185,11 @@ def note_constexpr_modify_const_type : Note<
   "in a constant expression">;
 def note_constexpr_access_volatile_type : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
-  "<ERROR>|<ERROR>|<ERROR>|<ERROR>}0 "
+  "<ERROR>|<ERROR>|<ERROR>|<ERROR>|read of}0 "
   "volatile-qualified type %1 is not allowed in a constant expression">;
 def note_constexpr_access_volatile_obj : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
-  "<ERROR>|<ERROR>|<ERROR>|<ERROR>}0 "
+  "<ERROR>|<ERROR>|<ERROR>|<ERROR>|read of}0 "
   "volatile %select{temporary|object %2|member %2}1 is not allowed in "
   "a constant expression">;
 def note_constexpr_volatile_here : Note<
@@ -197,7 +197,7 @@ def note_constexpr_volatile_here : Note<
 def note_constexpr_access_mutable : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of}0 "
+  "destruction of|read of}0 "
   "mutable member %1 is not allowed in a constant expression">;
 def note_constexpr_ltor_non_const_int : Note<
   "read of non-const variable %0 is not allowed in a constant expression">;
@@ -211,24 +211,24 @@ def note_constexpr_ltor_incomplete_type : Note<
 def note_constexpr_access_null : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of}0 "
+  "destruction of|read of}0 "
   "dereferenced null pointer is not allowed in a constant expression">;
 def note_constexpr_access_past_end : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of}0 "
+  "destruction of|read of}0 "
   "dereferenced one-past-the-end pointer is not allowed "
   "in a constant expression">;
 def note_constexpr_access_unsized_array : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of}0 "
+  "destruction of|read of}0 "
   "element of array without known bound "
   "is not allowed in a constant expression">;
 def note_constexpr_access_inactive_union_member : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|"
-  "construction of subobject of|destruction of}0 "
+  "construction of subobject of|destruction of|read of}0 "
   "member %1 of union with %select{active member %3|no active member}2 "
   "is not allowed in a constant expression">;
 def note_constexpr_union_member_change_during_init : Note<
@@ -237,18 +237,18 @@ def note_constexpr_union_member_change_during_init : Note<
 def note_constexpr_access_static_temporary : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|reconstruction of|"
-  "destruction of}0 temporary "
+  "destruction of|read of|read of}0 temporary "
   "is not allowed in a constant expression outside the expression that "
   "created the temporary">;
 def note_constexpr_access_unreadable_object : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of}0 "
+  "destruction of|read of}0 "
   "object '%1' whose value is not known">;
 def note_constexpr_access_deleted_object : Note<
   "%select{read of|read of|assignment to|increment of|decrement of|"
   "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of}0 "
+  "destruction of|read of}0 "
   "heap allocated object that has been deleted">;
 def note_constexpr_modify_global : Note<
   "a constant expression cannot modify an object that is visible outside "
@@ -425,6 +425,10 @@ def warn_is_constant_evaluated_always_true_constexpr : Warning<
   "'%0' will always evaluate to 'true' in a manifestly constant-evaluated expression">,
   InGroup<DiagGroup<"constant-evaluated">>;
 
+def err_invalid_is_within_lifetime : Error<
+  "'%0' cannot be called with %select{a null pointer|a function pointer|a one-past-the-end pointer|a pointer to an object whose lifetime has not begun or has ended}1"
+>;
+
 // inline asm related.
 let CategoryName = "Inline Assembly Issue" in {
   def err_asm_invalid_escape : Error<
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 9e82130c936096..3f60b865449539 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12040,6 +12040,9 @@ def err_builtin_launder_invalid_arg : Error<
   "%select{non-pointer|function pointer|void pointer}0 argument to "
   "'__builtin_launder' is not allowed">;
 
+def err_builtin_is_within_lifetime_invalid_arg : Error<
+  "non-pointer argument to '__builtin_is_within_lifetime' is not allowed">;
+
 def err_builtin_invalid_arg_type: Error <
   "%ordinal0 argument must be "
   "%select{a vector, integer or floating point type|a matrix|"
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f1aa19e4409e15..f3b31d87dde9b9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1507,7 +1507,7 @@ CallStackFrame::~CallStackFrame() {
 }
 
 static bool isRead(AccessKinds AK) {
-  return AK == AK_Read || AK == AK_ReadObjectRepresentation;
+  return AK == AK_Read || AK == AK_ReadObjectRepresentation || AK == AK_IsWithinLifetime;
 }
 
 static bool isModification(AccessKinds AK) {
@@ -1517,6 +1517,7 @@ static bool isModification(AccessKinds AK) {
   case AK_MemberCall:
   case AK_DynamicCast:
   case AK_TypeId:
+  case AK_IsWithinLifetime:
     return false;
   case AK_Assign:
   case AK_Increment:
@@ -1534,7 +1535,7 @@ static bool isAnyAccess(AccessKinds AK) {
 
 /// Is this an access per the C++ definition?
 static bool isFormalAccess(AccessKinds AK) {
-  return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy;
+  return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && AK != AK_IsWithinLifetime;
 }
 
 /// Is this kind of axcess valid on an indeterminate object value?
@@ -1546,6 +1547,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) {
     // These need the object's value.
     return false;
 
+  case AK_IsWithinLifetime:
   case AK_ReadObjectRepresentation:
   case AK_Assign:
   case AK_Construct:
@@ -3653,7 +3655,7 @@ struct CompleteObject {
     // In C++14 onwards, it is permitted to read a mutable member whose
     // lifetime began within the evaluation.
     // FIXME: Should we also allow this in C++11?
-    if (!Info.getLangOpts().CPlusPlus14)
+    if (!Info.getLangOpts().CPlusPlus14 && AK != AccessKinds::AK_IsWithinLifetime)
       return false;
     return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true);
   }
@@ -3706,6 +3708,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
     if ((O->isAbsent() && !(handler.AccessKind == AK_Construct && I == N)) ||
         (O->isIndeterminate() &&
          !isValidIndeterminateAccess(handler.AccessKind))) {
+      // Object has ended lifetime since pointer was formed
+      if (handler.AccessKind == AK_IsWithinLifetime)
+        return false;
       if (!Info.checkingPotentialConstantExpression())
         Info.FFDiag(E, diag::note_constexpr_access_uninit)
             << handler.AccessKind << O->isIndeterminate()
@@ -3852,6 +3857,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
             // Placement new onto an inactive union member makes it active.
             O->setUnion(Field, APValue());
           } else {
+            // Pointer to/into inactive union member: Not within lifetime
+            if (handler.AccessKind == AK_IsWithinLifetime)
+              return false;
             // FIXME: If O->getUnionValue() is absent, report that there's no
             // active union member rather than reporting the prior active union
             // member. We'll need to fix nullptr_t to not use APValue() as its
@@ -4051,9 +4059,11 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
     std::tie(Frame, Depth) =
         Info.getCallFrameAndDepth(LVal.getLValueCallIndex());
     if (!Frame) {
-      Info.FFDiag(E, diag::note_constexpr_lifetime_ended, 1)
-        << AK << LVal.Base.is<const ValueDecl*>();
-      NoteLValueLocation(Info, LVal.Base);
+      if (AK != AccessKinds::AK_IsWithinLifetime) {
+        Info.FFDiag(E, diag::note_constexpr_lifetime_ended, 1)
+            << AK << LVal.Base.is<const ValueDecl *>();
+        NoteLValueLocation(Info, LVal.Base);
+      }
       return CompleteObject();
     }
   }
@@ -4082,6 +4092,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
     // This is the object whose initializer we're evaluating, so its lifetime
     // started in the current evaluation.
     BaseVal = Info.EvaluatingDeclValue;
+    if (AK == AccessKinds::AK_IsWithinLifetime)
+      return CompleteObject();  // Not within lifetime
   } else if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl *>()) {
     // Allow reading from a GUID declaration.
     if (auto *GD = dyn_cast<MSGuidDecl>(D)) {
@@ -4213,7 +4225,8 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
   } else if (DynamicAllocLValue DA = LVal.Base.dyn_cast<DynamicAllocLValue>()) {
     std::optional<DynAlloc *> Alloc = Info.lookupDynamicAlloc(DA);
     if (!Alloc) {
-      Info.FFDiag(E, diag::note_constexpr_access_deleted_object) << AK;
+      if (AK != AccessKinds::AK_IsWithinLifetime)
+        Info.FFDiag(E, diag::note_constexpr_access_deleted_object) << AK;
       return CompleteObject();
     }
     return CompleteObject(LVal.Base, &(*Alloc)->Value,
@@ -11495,6 +11508,8 @@ class IntExprEvaluator
 
   bool ZeroInitialization(const Expr *E) { return Success(0, E); }
 
+  friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, const CallExpr *);
+
   //===--------------------------------------------------------------------===//
   //                            Visitor Methods
   //===--------------------------------------------------------------------===//
@@ -12546,6 +12561,11 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
     return Success(Info.InConstantContext, E);
   }
 
+  case Builtin::BI__builtin_is_within_lifetime:
+    if (auto result = EvaluateBuiltinIsWithinLifetime(*this, E))
+      return Success(*result, E);
+    return false;
+
   case Builtin::BI__builtin_ctz:
   case Builtin::BI__builtin_ctzl:
   case Builtin::BI__builtin_ctzll:
@@ -17008,3 +17028,58 @@ bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const {
   EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold);
   return EvaluateBuiltinStrLen(this, Result, Info);
 }
+
+namespace {
+  struct IsWithinLifetimeHandler {
+    EvalInfo &Info;
+    static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime;
+    using result_type = std::optional<bool>;
+    std::optional<bool> failed() { return std::nullopt; }
+    template<typename T>
+    std::optional<bool> found(T &Subobj, QualType SubobjType) {
+      return true;
+    }
+  };
+
+  std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, const CallExpr *E) {
+    EvalInfo& Info = IEE.Info;
+    //assert(Info.InConstantContext && "Call to consteval builtin not in constant context?");
+    assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime);
+    const Expr *Arg = E->getArg(0);
+    if (Arg->isValueDependent())
+      return std::nullopt;
+    LValue Val;
+    if (!EvaluatePointer(Arg, Val, Info))
+      return std::nullopt;
+
+    auto Error = [&](int Diag) {
+      const auto *Callee = Info.CurrentCall->getCallee();
+      bool CalledFromStd = Callee && Callee->isInStdNamespace() && Callee->getIdentifier() && Callee->getIdentifier()->isStr("is_within_lifetime");
+      Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() : E->getExprLoc(), diag::err_invalid_is_within_lifetime)
+          << (CalledFromStd ? "std::is_within_lifetime" : "__builtin_is_within_lifetime") << Diag;
+      return std::nullopt;
+    };
+    // C++2c [meta.const.eval]p4:
+    //   During the evaluation of an expression E as a core constant expression, a call to this function is ill-formed unless p points to an object that is usable in constant expressions or whose complete object's lifetime began within E.
+
+    // Make sure it points to an object
+    // nullptr does not point to an object
+    if (Val.isNullPointer() || Val.getLValueBase().isNull())
+      return Error(0);
+    QualType T = Val.getLValueBase().getType();
+    if (T->isFunctionType())
+      return Error(1);
+    assert(T->isObjectType());
+    // Hypothetical array element is not an object
+    if (Val.getLValueDesignator().isOnePastTheEnd())
+      return Error(2);
+    assert(Val.getLValueDesignator().isValidSubobject() && "Unchecked case for valid subobject");
+    // All other ill-formed values should have failed EvaluatePointer, so the object should be a pointer to an object
+    // that is usable in a constant expression or whose complete lifetime began within the expression
+    CompleteObject CO = findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T);
+    if (!CO)
+      return false;
+    IsWithinLifetimeHandler handler{ Info };
+    return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
+  }
+}
diff --git a/clang/lib/AST/Interp/State.h b/clang/lib/AST/Interp/State.h
index f1e8e3618f34fe..65039a67c7de49 100644
--- a/clang/lib/AST/Interp/State.h
+++ b/clang/lib/AST/Interp/State.h
@@ -34,6 +34,7 @@ enum AccessKinds {
   AK_TypeId,
   AK_Construct,
   AK_Destroy,
+  AK_IsWithinLifetime,
 };
 
 /// The order of this enum is important for diagnostics.
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 54789dde506919..1ca1d34c58b8aa 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2215,6 +2215,30 @@ static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
   return TheCall;
 }
 
+static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
+  if (checkArgCount(S, TheCall, 1))
+    return ExprError();
+
+  ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0));
+  if (Arg.isInvalid())
+    return ExprError();
+  QualType ParamTy = Arg.get()->getType();
+  TheCall->setArg(0, Arg.get());
+  TheCall->setType(S.Context.BoolTy);
+
+  // A call to this function is always ill-formed if the type is not a pointer to
+  // an object type. There is no Mandates: to that effect, so we can only
+  // issue an error if it is actually evaluated as part of a constant evaluation
+  // (e.g., `false ? true : std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine)
+  // However, `std::is_within_lifetime` will only take pointer types (allow non-const qualified too)
+  if (!ParamTy->isPointerType()) {
+    S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_builtin_is_within_lifetime_invalid_arg);
+    return ExprError();
+  }
+
+  return TheCall;
+}
+
 // Emit an error and return true if the current object format type is in the
 // list of unsupported types.
 static bool CheckBuiltinTargetNotInUnsupported(
@@ -2641,6 +2665,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
   }
   case Builtin::BI__builtin_launder:
     return BuiltinLaunder(*this, TheCall);
+  case Builtin::BI__builtin_is_within_lifetime:
+    return BuiltinIsWithinLifetime(*this, TheCall);
   case Builtin::BI__sync_fetch_and_add:
   case Builtin::BI__sync_fetch_and_add_1:
   case Builtin::BI__sync_fetch_and_add_2:
diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
new file mode 100644
index 00000000000000..fcc6303e051e6d
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -0,0 +1,211 @@
+// RUN: %clang_cc1 -std=c++2c -verify %s -fcxx-exceptions -Wno-unused -triple=x86_64-linux-gnu
+
+inline void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; }
+namespace std {
+template<typename T, typename... Args>
+constexpr T* construct_at(T* p, Args&&... args) { return ::new((void*)p) T(static_cast<Args&&>(args)...); }
+template<typename T>
+constexpr void destroy_at(T* p) { p->~T(); }
+template<typename T>
+struct allocator {
+  constexpr T* allocate(__SIZE_TYPE__ n) { return static_cast<T*>(::operator new(n * sizeof(T))); }
+  constexpr void deallocate(T* p, __SIZE_TYPE__) { ::operator delete(p); }
+};
+}
+
+constexpr void check_immediate() {
+  if consteval {
+    if (false) __builtin_is_within_lifetime(static_cast<int*>(nullptr));
+  }
+}
+auto* not_escalated = &check_immediate;
+
+template<typename T>
+constexpr bool check_immediate2() { // #check_immediate2
+  T i{};
+  if (false)
+    __builtin_is_within_lifetime(&i); // #check_immediate2-call
+  return true;
+}
+static_assert(check_immediate2<int>());
+auto* escalated = &check_immediate2<int>;
+// expected-error at -1 {{cannot take address of immediate function 'check_immediate2<int>' outside of an immediate invocation}}
+//   expected-note@#check_immediate2 {{declared here}}
+//   expected-note@#check_immediate2-call {{'check_immediate2<int>' is an immediate function because its body contains a call to a consteval function '__builtin_is_within_lifetime' and that call is not a constant expression}}
+
+consteval bool test_union(int& i, char& c) {
+  if (__builtin_is_within_lifetime(&i) || __builtin_is_within_lifetime(&c))
+    return false;
+  std::construct_at(&c, 1);
+  if (__builtin_is_within_lifetime(&i) || !__builtin_is_within_lifetime(&c))
+    return false;
+  std::construct_at(&i, 3);
+  if (!__builtin_is_within_lifetime(&i) || __builtin_is_within_lifetime(&c))
+    return false;
+  return true;
+}
+
+static_assert([]{
+  union { int i; char c; } u;
+  return test_union(u.i, u.c);
+}());
+static_assert([]{
+  union { int i; char c; };
+  return test_union(i, c);
+}());
+static_assert([]{
+  struct { union { int i; char c; }; } u;
+  return test_union(u.i, u.c);
+}());
+static_assert([]{
+  struct { union { int i; char c; } u; } r;
+  return test_union(r.u.i, r.u.c);
+}());
+
+consteval bool test_nested() {
+  union {
+    union { int i; char c; } u;
+    long l;
+  };
+  if (__builtin_is_within_lifetime(&l) || __builtin_is_within_lifetime(&u) || __builtin_is_within_lifetime(&u.i) || __builtin_is_within_lifetime(&u.c))
+    return false;
+  std::construct_at(&l);
+  if (!__builtin_is_within_lifetime(&l) || __builtin_is_within_lifetime(&u) || __builtin_is_within_lifetime(&u.i) || __builtin_is_within_lifetime(&u.c))
+    return false;
+  std::construct_at(&u);
+  std::construct_at(&u.i);
+  if (__builtin_is_within_lifetime(&l) || !__builtin_is_within_lifetime(&u) || !__builtin_is_within_lifetime(&u.i) || __builtin_is_within_lifetime(&u.c))
+    return false;
+  std::construct_at(&u.c);
+  if (__builtin_is_within_lifetime(&l) || !__builtin_is_within_lifetime(&u) || __builtin_is_within_lifetime(&u.i) || !__builtin_is_within_lifetime(&u.c))
+    return false;
+  return true;
+}
+static_assert(test_nested());
+
+consteval bool test_dynamic() {
+  std::allocator<int> a;
+  int* p = a.allocate(1);
+  if (__builtin_is_within_lifetime(p))
+    return false;
+  std::construct_at(p);
+  if (!__builtin_is_within_lifetime(p))
+    return false;
+  std::destroy_at(p);
+  if (__builtin_is_within_lifetime(p))
+    return false;
+  a.deallocate(p, 1);
+  if (__builtin_is_within_lifetime(p))
+    return false;
+  return true;
+}
+static_assert(test_dynamic());
+
+constexpr bool self = __builtin_is_within_lifetime(&self);
+// expected-error at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+//   expected-note at -2 {{initializer of 'self' is unknown}}
+//   expected-note at -3 {{declared here}}
+constexpr int external{};
+static_assert(__builtin_is_within_lifetime(&external));
+bool not_constexpr() {
+    return __builtin_is_within_lifetime(&external);
+}
+constexpr struct {
+  union {
+    int i;
+    char c;
+  };
+  mutable int mi;  // #x-mi
+} x1{ .c = 2 };
+static_assert(!__builtin_is_within_lifetime(&x1.i));
+static_assert(__builtin_is_within_lifetime(&x1.c));
+static_assert(__builtin_is_within_lifetime(&x1.mi));
+// expected-error at -1 {{static assertion expression is not an integral constant expression}}
+//   expected-note at -2 {{read of mutable member 'mi' is not allowed in a constant expression}}
+//   expected-note@#x-mi {{declared here}}
+
+constexpr struct {
+  bool a = __builtin_is_within_lifetime(&b);
+  bool b = __builtin_is_within_lifetime(&a);
+  bool c = __builtin_is_within_lifetime(this);
+} x2;
+static_assert(!x2.a);
+static_assert(!x2.b);
+static_assert(!x2.c);
+
+struct X3 {
+  bool a, b, c, d;
+  consteval X3();
+};
+extern const X3 x3;
+consteval X3::X3() : a(__builtin_is_within_lifetime(&b)), b(false), c(__builtin_is_within_lifetime(&b)) {
+  b = __builtin_is_within_lifetime(&b);
+  d = __builtin_is_within_lifetime(&x3.c);
+}
+constexpr X3 x3{};
+static_assert(!x3.a);
+static_assert(!x3.b);
+static_assert(!x3.c);
+static_assert(!x3.d);
+
+constexpr int i = 2;
+static_assert(__builtin_is_within_lifetime(const_cast<int*>(&i)));
+static_assert(__builtin_is_within_lifetime(const_cast<volatile int*>(&i)));
+static_assert(__builtin_is_within_lifetime(static_cast<const void*>(&i)));
+
+constexpr int arr[2]{};
+static_assert(__builtin_is_within_lifetime(arr));
+static_assert(__builtin_is_within_lifetime(arr + 0));
+static_assert(__builtin_is_within_lifetime(arr + 1));
+void f() {
+  __builtin_is_within_lifetime(&i + 1);
+// expected-error at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+// expected-error at -2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}}
+  __builtin_is_within_lifetime(arr + 2);
+// expected-error at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+// expected-error at -2 {{'__builtin_is_within_lifetime' cannot be called with a one-past-the-end pointer}}
+}
+
+template<typename T>
+consteval bool allow_bad_types_unless_used(bool b, T* p) {
+  if (b) {
+    __builtin_is_within_lifetime(p); // #bad_type_used
+  }
+  return true;
+}
+void fn();
+static_assert(allow_bad_types_unless_used<void()>(false, &fn));
+void g() {
+  allow_bad_types_unless_used<void()>(true, &fn);
+// expected-error at -1 {{call to consteval function 'allow_bad_types_unless_used<void ()>' is not a constant expression}}
+// expected-error@#bad_type_used {{'__builtin_is_within_lifetime' cannot be called with a function pointer}}
+}
+
+struct OptBool {
+  union { bool b; char c; };
+
+  // note: this assumes common implementation properties for bool and char:
+  // * sizeof(bool) == sizeof(char), and
+  // * the value representations for true and false are distinct
+  //   from the value representation for 2
+  constexpr OptBool() : c(2) { }
+  constexpr OptBool(bool b) : b(b) { }
+
+  constexpr auto has_value() const -> bool {
+    if consteval {
+      return __builtin_is_within_lifetime(&b);   // during constant evaluation, cannot read from c
+    } else {
+      return c != 2;                        // during runtime, must read from c
+    }
+  }
+
+  constexpr auto operator*() const -> const bool& {
+    return b;
+  }
+};
+
+constexpr OptBool disengaged;
+constexpr OptBool engaged(true);
+static_assert(!disengaged.has_value());
+static_assert(engaged.has_value());
+static_assert(*engaged);

>From 88c68cdffe8bf94d8ecca651d6162e8b601cdc38 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 14 May 2024 18:29:48 +0100
Subject: [PATCH 03/11] No longer declare constexpr builtins as constexpr

---
 clang/lib/Sema/SemaDecl.cpp        |  6 ++----
 clang/lib/Sema/SemaDeclCXX.cpp     | 18 +++++-------------
 clang/test/Sema/builtin-redecl.cpp | 15 +++++----------
 3 files changed, 12 insertions(+), 27 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 6b0a04585928a5..7ca02b5bbf6249 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2410,10 +2410,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
   }
 
   ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
-  if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isConstantEvaluated(ID)) {
-    ConstexprKind = ConstexprSpecKind::Constexpr;
-    if (Context.BuiltinInfo.isImmediate(ID))
-      ConstexprKind = ConstexprSpecKind::Consteval;
+  if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isImmediate(ID)) {
+    ConstexprKind = ConstexprSpecKind::Consteval;
   }
 
   FunctionDecl *New = FunctionDecl::Create(
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 1b558d70f9b487..53238d355ea091 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -676,19 +676,11 @@ bool Sema::MergeCXXFunctionDecl(FunctionDecl *New, FunctionDecl *Old,
   // template has a constexpr specifier then all its declarations shall
   // contain the constexpr specifier.
   if (New->getConstexprKind() != Old->getConstexprKind()) {
-    if (Old->getBuiltinID() &&
-        Old->getConstexprKind() == ConstexprSpecKind::Constexpr &&
-        New->getConstexprKind() == ConstexprSpecKind::Unspecified) {
-      // Except allow redeclaring a builtin as non-constexpr to match C
-      // redeclarations which will not be constexpr
-      New->setConstexprKind(ConstexprSpecKind::Constexpr);
-    } else {
-      Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
-          << New << static_cast<int>(New->getConstexprKind())
-          << static_cast<int>(Old->getConstexprKind());
-      Diag(Old->getLocation(), diag::note_previous_declaration);
-      Invalid = true;
-    }
+    Diag(New->getLocation(), diag::err_constexpr_redecl_mismatch)
+        << New << static_cast<int>(New->getConstexprKind())
+        << static_cast<int>(Old->getConstexprKind());
+    Diag(Old->getLocation(), diag::note_previous_declaration);
+    Invalid = true;
   } else if (!Old->getMostRecentDecl()->isInlined() && New->isInlined() &&
              Old->isDefined(Def) &&
              // If a friend function is inlined but does not have 'inline'
diff --git a/clang/test/Sema/builtin-redecl.cpp b/clang/test/Sema/builtin-redecl.cpp
index 31409a4d46a65b..323c63e2028833 100644
--- a/clang/test/Sema/builtin-redecl.cpp
+++ b/clang/test/Sema/builtin-redecl.cpp
@@ -14,18 +14,13 @@ void __builtin_va_copy(double d);
 // expected-error at +2 {{cannot redeclare builtin function '__builtin_va_end'}}
 // expected-note at +1 {{'__builtin_va_end' is a builtin with type}}
 void __builtin_va_end(__builtin_va_list);
+// RUN: %clang_cc1 %s -fsyntax-only -verify 
+// RUN: %clang_cc1 %s -fsyntax-only -verify -x c
 
 void __va_start(__builtin_va_list*, ...);
 
-          void *__builtin_assume_aligned(const void *, size_t, ...);
 #ifdef __cplusplus
-constexpr void *__builtin_assume_aligned(const void *, size_t, ...);
-          void *__builtin_assume_aligned(const void *, size_t, ...) noexcept;
-constexpr void *__builtin_assume_aligned(const void *, size_t, ...) noexcept;
-          void *__builtin_assume_aligned(const void *, size_t, ...) throw();
-constexpr void *__builtin_assume_aligned(const void *, size_t, ...) throw();
-
-// expected-error at +1 {{constexpr declaration of '__builtin_calloc' follows non-constexpr declaration}}
-constexpr void *__builtin_calloc(size_t, size_t);
-// expected-note at -1 {{previous declaration is here}}
+void *__builtin_assume_aligned(const void *, size_t, ...) noexcept;
+#else
+void *__builtin_assume_aligned(const void *, size_t, ...);
 #endif

>From ec88839cb34bf2a5f57edd376e30d8c08740f855 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 14 May 2024 18:57:09 +0100
Subject: [PATCH 04/11] Prevent consteval builtins from being available when
 consteval doesn't make sense (!CPlusPlus20)

---
 clang/include/clang/Basic/Builtins.def    | 1 +
 clang/include/clang/Basic/BuiltinsBase.td | 2 +-
 clang/lib/Basic/Builtins.cpp              | 3 +++
 clang/lib/Sema/SemaDecl.cpp               | 3 ++-
 4 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def
index f356f881d5ef9b..f3d76425733805 100644
--- a/clang/include/clang/Basic/Builtins.def
+++ b/clang/include/clang/Basic/Builtins.def
@@ -100,3 +100,4 @@
 //                      M_0, ..., M_k as payload
 //  z -> this is a function in (possibly-versioned) namespace std
 //  E -> this function can be constant evaluated by Clang frontend
+//  G -> this is a C++20 consteval function
diff --git a/clang/include/clang/Basic/BuiltinsBase.td b/clang/include/clang/Basic/BuiltinsBase.td
index 1196b9e15c10dc..58dee22fc0a450 100644
--- a/clang/include/clang/Basic/BuiltinsBase.td
+++ b/clang/include/clang/Basic/BuiltinsBase.td
@@ -70,7 +70,7 @@ class VScanfFormat<int I> : IndexedAttribute<"S", I>;
 
 // Builtin can be constant evaluated
 def Constexpr : Attribute<"E">;
-// Builtin is immediate and must be constant evaluated. Implies Constexpr.
+// Builtin is immediate and must be constant evaluated. Implies Constexpr, and will only be supported in C++20 mode.
 def Consteval : Attribute<"EG">;
 
 // Builtin kinds
diff --git a/clang/lib/Basic/Builtins.cpp b/clang/lib/Basic/Builtins.cpp
index b116abbe034f7a..7116e27cd95463 100644
--- a/clang/lib/Basic/Builtins.cpp
+++ b/clang/lib/Basic/Builtins.cpp
@@ -119,6 +119,9 @@ static bool builtinIsSupported(const Builtin::Info &BuiltinInfo,
   /* CPlusPlus Unsupported */
   if (!LangOpts.CPlusPlus && BuiltinInfo.Langs == CXX_LANG)
     return false;
+  /* consteval Unsupported */
+  if (!LangOpts.CPlusPlus20 && strchr(BuiltinInfo.Attributes, 'G') != nullptr)
+    return false;
   return true;
 }
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 7ca02b5bbf6249..d651ee2f502b18 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2410,7 +2410,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
   }
 
   ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
-  if (getLangOpts().CPlusPlus && Context.BuiltinInfo.isImmediate(ID)) {
+  if (Context.BuiltinInfo.isImmediate(ID)) {
+    assert(getLangOpts().CPlusPlus20 && "consteval builtins should only be available in C++20 mode");
     ConstexprKind = ConstexprSpecKind::Consteval;
   }
 

>From b498e90f6bd72e2b9044aef9a4c049845a68bc0d Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 14 May 2024 19:06:46 +0100
Subject: [PATCH 05/11] clang-format

---
 clang/lib/Sema/SemaDecl.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8554cb59729ff3..c24ebc3ef4ebbb 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2373,7 +2373,8 @@ FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
 
   ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified;
   if (Context.BuiltinInfo.isImmediate(ID)) {
-    assert(getLangOpts().CPlusPlus20 && "consteval builtins should only be available in C++20 mode");
+    assert(getLangOpts().CPlusPlus20 &&
+           "consteval builtins should only be available in C++20 mode");
     ConstexprKind = ConstexprSpecKind::Consteval;
   }
 

>From bfb918623032748df99ccf948e7648de416477d0 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 10:52:57 +0100
Subject: [PATCH 06/11] Add test specifically for consteval builtins

---
 clang/lib/CodeGen/CGBuiltin.cpp          |  3 ++
 clang/test/SemaCXX/consteval-builtin.cpp | 61 ++++++++++++++++++++++++
 2 files changed, 64 insertions(+)
 create mode 100644 clang/test/SemaCXX/consteval-builtin.cpp

diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index e251091c6ce3ee..513c6b6385838d 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -2540,6 +2540,9 @@ static RValue EmitHipStdParUnsupportedBuiltin(CodeGenFunction *CGF,
 RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
                                         const CallExpr *E,
                                         ReturnValueSlot ReturnValue) {
+  assert(!getContext().BuiltinInfo.isImmediate(BuiltinID) &&
+         "Should not codegen for consteval builtins");
+
   const FunctionDecl *FD = GD.getDecl()->getAsFunction();
   // See if we can constant fold this builtin.  If so, don't emit it at all.
   // TODO: Extend this handling to all builtin calls that we can constant-fold.
diff --git a/clang/test/SemaCXX/consteval-builtin.cpp b/clang/test/SemaCXX/consteval-builtin.cpp
new file mode 100644
index 00000000000000..c8f89fc7c892c7
--- /dev/null
+++ b/clang/test/SemaCXX/consteval-builtin.cpp
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -Wno-unused %s -verify=cxx20-cxx26
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -Wno-unused %s -verify=cxx20,cxx20-cxx26
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17
+// RUN: %clang_cc1 -std=c++14 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only -Wno-unused %s -verify=precxx20,cxx11-cxx17
+// RUN: %clang_cc1 -std=c++03 -fsyntax-only -Wno-unused %s -verify=precxx20
+// RUN: %clang_cc1 -std=c++98 -fsyntax-only -Wno-unused %s -verify=precxx20
+// RUN: %clang_cc1 -x c -std=c23 -fsyntax-only -Wno-unused %s -verify=c
+
+#if __cplusplus < 201103L
+#define static_assert __extension__ _Static_assert
+#define CONSTEXPR11
+#else
+#define CONSTEXPR11 constexpr
+#endif
+
+static const int i1 = 0;
+static_assert(__builtin_is_within_lifetime(&i1), "");
+// precxx20-error at -1 {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+// c-error at -2 {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+
+#if !defined(__cplusplus) || __cplusplus >= 201102L
+constexpr int i2 = 0;
+static_assert(__builtin_is_within_lifetime(&i2), "");
+// cxx11-cxx17-error at -1 {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+// c-error at -2 {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+#endif
+
+#ifdef __cplusplus
+template<typename T>
+CONSTEXPR11 bool f1(T i) {  // #f1
+  return __builtin_is_within_lifetime(&i);  // #f1-consteval-call
+}
+
+bool(&fp1)(int) = f1<int>;
+// cxx20-cxx26-error at -1 {{cannot take address of immediate function 'f1<int>' outside of an immediate invocation}}
+//   cxx20-cxx26-note@#f1 {{declared here}}
+//   cxx20-cxx26-note@#f1-consteval-call {{'f1<int>' is an immediate function because its body contains a call to a consteval function '__builtin_is_within_lifetime' and that call is not a constant expression}}
+// precxx20-error@#f1-consteval-call {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+//   precxx20-note at -5 {{in instantiation of function template specialization 'f1<int>' requested here}}
+#else
+void f1(int i) {
+  __builtin_is_within_lifetime(&i);
+// c-error at -1 {{use of undeclared identifier '__builtin_is_within_lifetime'}}
+}
+#endif
+
+#if __cplusplus >= 202002L
+constexpr void f2() {
+  int i = 0;
+  if consteval {  // cxx20-warning {{consteval if}}
+    __builtin_is_within_lifetime(&i);
+  }
+}
+void(&fp2)() = f2;
+
+constexpr void f3() {
+  __builtin_is_within_lifetime(&i1);
+}
+void(&fp3)() = f3;
+#endif

>From 53ebf025473dbe48fa20a0f4fea443595b0cec3a Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 11:59:36 +0100
Subject: [PATCH 07/11] Add more tests

---
 .../SemaCXX/builtin-is-within-lifetime.cpp    | 93 ++++++++++++++-----
 1 file changed, 68 insertions(+), 25 deletions(-)

diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index fcc6303e051e6d..c6190aff84abd9 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -1,4 +1,6 @@
-// RUN: %clang_cc1 -std=c++2c -verify %s -fcxx-exceptions -Wno-unused -triple=x86_64-linux-gnu
+// RUN: %clang_cc1 -std=c++20 -Wno-unused %s -verify=expected,cxx20
+// RUN: %clang_cc1 -std=c++23 -Wno-unused %s -verify=expected
+// RUN: %clang_cc1 -std=c++2c -Wno-unused %s -verify=expected
 
 inline void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; }
 namespace std {
@@ -11,28 +13,9 @@ struct allocator {
   constexpr T* allocate(__SIZE_TYPE__ n) { return static_cast<T*>(::operator new(n * sizeof(T))); }
   constexpr void deallocate(T* p, __SIZE_TYPE__) { ::operator delete(p); }
 };
+using nullptr_t = decltype(nullptr);
 }
 
-constexpr void check_immediate() {
-  if consteval {
-    if (false) __builtin_is_within_lifetime(static_cast<int*>(nullptr));
-  }
-}
-auto* not_escalated = &check_immediate;
-
-template<typename T>
-constexpr bool check_immediate2() { // #check_immediate2
-  T i{};
-  if (false)
-    __builtin_is_within_lifetime(&i); // #check_immediate2-call
-  return true;
-}
-static_assert(check_immediate2<int>());
-auto* escalated = &check_immediate2<int>;
-// expected-error at -1 {{cannot take address of immediate function 'check_immediate2<int>' outside of an immediate invocation}}
-//   expected-note@#check_immediate2 {{declared here}}
-//   expected-note@#check_immediate2-call {{'check_immediate2<int>' is an immediate function because its body contains a call to a consteval function '__builtin_is_within_lifetime' and that call is not a constant expression}}
-
 consteval bool test_union(int& i, char& c) {
   if (__builtin_is_within_lifetime(&i) || __builtin_is_within_lifetime(&c))
     return false;
@@ -101,10 +84,70 @@ consteval bool test_dynamic() {
 }
 static_assert(test_dynamic());
 
+consteval bool test_automatic() {
+  int* p;
+  {
+    int x = 0;
+    p = &x;
+    if (!__builtin_is_within_lifetime(p))
+      return false;
+  }
+  {
+    int x = 0;
+    if (__builtin_is_within_lifetime(p))
+      return false;
+  }
+  if (__builtin_is_within_lifetime(p))
+    return false;
+  {
+    int x[4];
+    p = &x[2];
+    if (!__builtin_is_within_lifetime(p))
+      return false;
+  }
+  if (__builtin_is_within_lifetime(p))
+    return false;
+  std::nullptr_t* q;
+  {
+    std::nullptr_t np = nullptr;
+    q = &np;
+    if (!__builtin_is_within_lifetime(q))
+      return false;
+  }
+  if (__builtin_is_within_lifetime(q))
+    return false;
+  return true;
+}
+static_assert(test_automatic());
+
+consteval bool test_indeterminate() {
+  int x;
+  if (!__builtin_is_within_lifetime(&x))
+    return false;
+  bool b = true;
+  unsigned char c = __builtin_bit_cast(unsigned char, b);
+  if (!__builtin_is_within_lifetime(&c))
+    return false;
+  struct {} padding;
+  unsigned char y = __builtin_bit_cast(unsigned char, padding);
+  if (!__builtin_is_within_lifetime(&y))
+    return false;
+  return true;
+}
+static_assert(test_indeterminate());
+
+consteval bool test_volatile() {
+  int x;
+  if (!__builtin_is_within_lifetime(static_cast<volatile int*>(&x)) || !__builtin_is_within_lifetime(static_cast<volatile void*>(&x)))
+    return false;
+  volatile int y;
+  if (!__builtin_is_within_lifetime(const_cast<int*>(&y)) || !__builtin_is_within_lifetime(const_cast<void*>(static_cast<volatile void*>(&y))))
+    return false;
+  return true;
+}
+static_assert(test_volatile());
+
 constexpr bool self = __builtin_is_within_lifetime(&self);
-// expected-error at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
-//   expected-note at -2 {{initializer of 'self' is unknown}}
-//   expected-note at -3 {{declared here}}
 constexpr int external{};
 static_assert(__builtin_is_within_lifetime(&external));
 bool not_constexpr() {
@@ -192,7 +235,7 @@ struct OptBool {
   constexpr OptBool(bool b) : b(b) { }
 
   constexpr auto has_value() const -> bool {
-    if consteval {
+    if consteval {  // cxx20-warning {{consteval if}}
       return __builtin_is_within_lifetime(&b);   // during constant evaluation, cannot read from c
     } else {
       return c != 2;                        // during runtime, must read from c

>From 6805d86090ea84d4c5ed4f7ab72dca9f9ba77bc7 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 12:05:46 +0100
Subject: [PATCH 08/11] clang-format

---
 clang/lib/AST/ExprConstant.cpp  | 128 ++++++++++++++++++--------------
 clang/lib/Sema/SemaChecking.cpp |  13 ++--
 2 files changed, 81 insertions(+), 60 deletions(-)

diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f3b31d87dde9b9..ffe2f172a9a5a9 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -1507,7 +1507,8 @@ CallStackFrame::~CallStackFrame() {
 }
 
 static bool isRead(AccessKinds AK) {
-  return AK == AK_Read || AK == AK_ReadObjectRepresentation || AK == AK_IsWithinLifetime;
+  return AK == AK_Read || AK == AK_ReadObjectRepresentation ||
+         AK == AK_IsWithinLifetime;
 }
 
 static bool isModification(AccessKinds AK) {
@@ -1535,7 +1536,8 @@ static bool isAnyAccess(AccessKinds AK) {
 
 /// Is this an access per the C++ definition?
 static bool isFormalAccess(AccessKinds AK) {
-  return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy && AK != AK_IsWithinLifetime;
+  return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy &&
+         AK != AK_IsWithinLifetime;
 }
 
 /// Is this kind of axcess valid on an indeterminate object value?
@@ -3655,7 +3657,8 @@ struct CompleteObject {
     // In C++14 onwards, it is permitted to read a mutable member whose
     // lifetime began within the evaluation.
     // FIXME: Should we also allow this in C++11?
-    if (!Info.getLangOpts().CPlusPlus14 && AK != AccessKinds::AK_IsWithinLifetime)
+    if (!Info.getLangOpts().CPlusPlus14 &&
+        AK != AccessKinds::AK_IsWithinLifetime)
       return false;
     return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true);
   }
@@ -4093,7 +4096,7 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E,
     // started in the current evaluation.
     BaseVal = Info.EvaluatingDeclValue;
     if (AK == AccessKinds::AK_IsWithinLifetime)
-      return CompleteObject();  // Not within lifetime
+      return CompleteObject(); // Not within lifetime
   } else if (const ValueDecl *D = LVal.Base.dyn_cast<const ValueDecl *>()) {
     // Allow reading from a GUID declaration.
     if (auto *GD = dyn_cast<MSGuidDecl>(D)) {
@@ -11508,7 +11511,8 @@ class IntExprEvaluator
 
   bool ZeroInitialization(const Expr *E) { return Success(0, E); }
 
-  friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &, const CallExpr *);
+  friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &,
+                                                             const CallExpr *);
 
   //===--------------------------------------------------------------------===//
   //                            Visitor Methods
@@ -17030,56 +17034,70 @@ bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const {
 }
 
 namespace {
-  struct IsWithinLifetimeHandler {
-    EvalInfo &Info;
-    static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime;
-    using result_type = std::optional<bool>;
-    std::optional<bool> failed() { return std::nullopt; }
-    template<typename T>
-    std::optional<bool> found(T &Subobj, QualType SubobjType) {
-      return true;
-    }
-  };
+struct IsWithinLifetimeHandler {
+  EvalInfo &Info;
+  static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime;
+  using result_type = std::optional<bool>;
+  std::optional<bool> failed() { return std::nullopt; }
+  template <typename T>
+  std::optional<bool> found(T &Subobj, QualType SubobjType) {
+    return true;
+  }
+};
 
-  std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, const CallExpr *E) {
-    EvalInfo& Info = IEE.Info;
-    //assert(Info.InConstantContext && "Call to consteval builtin not in constant context?");
-    assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime);
-    const Expr *Arg = E->getArg(0);
-    if (Arg->isValueDependent())
-      return std::nullopt;
-    LValue Val;
-    if (!EvaluatePointer(Arg, Val, Info))
-      return std::nullopt;
+std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE,
+                                                    const CallExpr *E) {
+  EvalInfo &Info = IEE.Info;
+  // assert(Info.InConstantContext && "Call to consteval builtin not in constant
+  // context?");
+  assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime);
+  const Expr *Arg = E->getArg(0);
+  if (Arg->isValueDependent())
+    return std::nullopt;
+  LValue Val;
+  if (!EvaluatePointer(Arg, Val, Info))
+    return std::nullopt;
 
-    auto Error = [&](int Diag) {
-      const auto *Callee = Info.CurrentCall->getCallee();
-      bool CalledFromStd = Callee && Callee->isInStdNamespace() && Callee->getIdentifier() && Callee->getIdentifier()->isStr("is_within_lifetime");
-      Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() : E->getExprLoc(), diag::err_invalid_is_within_lifetime)
-          << (CalledFromStd ? "std::is_within_lifetime" : "__builtin_is_within_lifetime") << Diag;
-      return std::nullopt;
-    };
-    // C++2c [meta.const.eval]p4:
-    //   During the evaluation of an expression E as a core constant expression, a call to this function is ill-formed unless p points to an object that is usable in constant expressions or whose complete object's lifetime began within E.
-
-    // Make sure it points to an object
-    // nullptr does not point to an object
-    if (Val.isNullPointer() || Val.getLValueBase().isNull())
-      return Error(0);
-    QualType T = Val.getLValueBase().getType();
-    if (T->isFunctionType())
-      return Error(1);
-    assert(T->isObjectType());
-    // Hypothetical array element is not an object
-    if (Val.getLValueDesignator().isOnePastTheEnd())
-      return Error(2);
-    assert(Val.getLValueDesignator().isValidSubobject() && "Unchecked case for valid subobject");
-    // All other ill-formed values should have failed EvaluatePointer, so the object should be a pointer to an object
-    // that is usable in a constant expression or whose complete lifetime began within the expression
-    CompleteObject CO = findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T);
-    if (!CO)
-      return false;
-    IsWithinLifetimeHandler handler{ Info };
-    return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
-  }
+  auto Error = [&](int Diag) {
+    const auto *Callee = Info.CurrentCall->getCallee();
+    bool CalledFromStd = Callee && Callee->isInStdNamespace() &&
+                         Callee->getIdentifier() &&
+                         Callee->getIdentifier()->isStr("is_within_lifetime");
+    Info.report(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin()
+                              : E->getExprLoc(),
+                diag::err_invalid_is_within_lifetime)
+        << (CalledFromStd ? "std::is_within_lifetime"
+                          : "__builtin_is_within_lifetime")
+        << Diag;
+    return std::nullopt;
+  };
+  // C++2c [meta.const.eval]p4:
+  //   During the evaluation of an expression E as a core constant expression, a
+  //   call to this function is ill-formed unless p points to an object that is
+  //   usable in constant expressions or whose complete object's lifetime began
+  //   within E.
+
+  // Make sure it points to an object
+  // nullptr does not point to an object
+  if (Val.isNullPointer() || Val.getLValueBase().isNull())
+    return Error(0);
+  QualType T = Val.getLValueBase().getType();
+  if (T->isFunctionType())
+    return Error(1);
+  assert(T->isObjectType());
+  // Hypothetical array element is not an object
+  if (Val.getLValueDesignator().isOnePastTheEnd())
+    return Error(2);
+  assert(Val.getLValueDesignator().isValidSubobject() &&
+         "Unchecked case for valid subobject");
+  // All other ill-formed values should have failed EvaluatePointer, so the
+  // object should be a pointer to an object that is usable in a constant
+  // expression or whose complete lifetime began within the expression
+  CompleteObject CO =
+      findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T);
+  if (!CO)
+    return false;
+  IsWithinLifetimeHandler handler{Info};
+  return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
 }
+} // namespace
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 127175f9ceeba9..df2dd9beb75287 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -2227,13 +2227,16 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
   TheCall->setArg(0, Arg.get());
   TheCall->setType(S.Context.BoolTy);
 
-  // A call to this function is always ill-formed if the type is not a pointer to
-  // an object type. There is no Mandates: to that effect, so we can only
+  // A call to this function is always ill-formed if the type is not a pointer
+  // to an object type. There is no Mandates: to that effect, so we can only
   // issue an error if it is actually evaluated as part of a constant evaluation
-  // (e.g., `false ? true : std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine)
-  // However, `std::is_within_lifetime` will only take pointer types (allow non-const qualified too)
+  // (e.g., `false ? true :
+  // std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine)
+  // However, `std::is_within_lifetime` will only take pointer types (allow
+  // non-const qualified too)
   if (!ParamTy->isPointerType()) {
-    S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_builtin_is_within_lifetime_invalid_arg);
+    S.Diag(TheCall->getArg(0)->getExprLoc(),
+           diag::err_builtin_is_within_lifetime_invalid_arg);
     return ExprError();
   }
 

>From d50febdea9b7b4a6b59955765b43bb9670cdc68f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 20 May 2024 13:14:41 +0100
Subject: [PATCH 09/11] Consolidate diagnostics text + test invalid calls

---
 .../include/clang/Basic/DiagnosticASTKinds.td | 52 +++++++------------
 clang/lib/Sema/SemaExpr.cpp                   |  5 +-
 .../SemaCXX/builtin-is-within-lifetime.cpp    | 22 +++++++-
 clang/test/SemaCXX/consteval-builtin.cpp      | 14 +++++
 4 files changed, 55 insertions(+), 38 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 7f8908e45663b9..ebd8bb75ef6be1 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -166,15 +166,17 @@ def note_constexpr_heap_alloc_limit_exceeded : Note<
 def note_constexpr_this : Note<
   "%select{|implicit }0use of 'this' pointer is only allowed within the "
   "evaluation of a call to a 'constexpr' member function">;
+def access_kind : TextSubstitution<
+  "%select{read of|read of|assignment to|increment of|decrement of|member call on|dynamic_cast of|typeid applied to|construction of|destruction of|read of}0">;
+def access_kind_subobject : TextSubstitution<
+  "%select{read of|read of|assignment to|increment of|decrement of|member call on|dynamic_cast of|typeid applied to|construction of subobject of|destruction of|read of}0">;
+def access_kind_volatile : TextSubstitution<
+  "%select{read of|read of|assignment to|increment of|decrement of|<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>}0">;
 def note_constexpr_lifetime_ended : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of|read of}0 %select{temporary|variable}1 whose "
+  "%sub{access_kind}0 %select{temporary|variable}1 whose "
   "%plural{8:storage duration|:lifetime}0 has ended">;
 def note_constexpr_access_uninit : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|"
-  "construction of subobject of|destruction of|read of}0 "
+  "%sub{access_kind_subobject}0 "
   "%select{object outside its lifetime|uninitialized object}1 "
   "is not allowed in a constant expression">;
 def note_constexpr_use_uninit_reference : Note<
@@ -184,20 +186,16 @@ def note_constexpr_modify_const_type : Note<
   "modification of object of const-qualified type %0 is not allowed "
   "in a constant expression">;
 def note_constexpr_access_volatile_type : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "<ERROR>|<ERROR>|<ERROR>|<ERROR>|read of}0 "
+  "%sub{access_kind_volatile}0 "
   "volatile-qualified type %1 is not allowed in a constant expression">;
 def note_constexpr_access_volatile_obj : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "<ERROR>|<ERROR>|<ERROR>|<ERROR>|read of}0 "
+  "%sub{access_kind_volatile}0 "
   "volatile %select{temporary|object %2|member %2}1 is not allowed in "
   "a constant expression">;
 def note_constexpr_volatile_here : Note<
   "volatile %select{temporary created|object declared|member declared}0 here">;
 def note_constexpr_access_mutable : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of|read of}0 "
+  "%sub{access_kind}0 "
   "mutable member %1 is not allowed in a constant expression">;
 def note_constexpr_ltor_non_const_int : Note<
   "read of non-const variable %0 is not allowed in a constant expression">;
@@ -209,46 +207,32 @@ def note_constexpr_ltor_non_constexpr : Note<
 def note_constexpr_ltor_incomplete_type : Note<
   "read of incomplete type %0 is not allowed in a constant expression">;
 def note_constexpr_access_null : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of|read of}0 "
+  "%sub{access_kind}0 "
   "dereferenced null pointer is not allowed in a constant expression">;
 def note_constexpr_access_past_end : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of|read of}0 "
+  "%sub{access_kind}0 "
   "dereferenced one-past-the-end pointer is not allowed "
   "in a constant expression">;
 def note_constexpr_access_unsized_array : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of|read of}0 "
+  "%sub{access_kind}0 "
   "element of array without known bound "
   "is not allowed in a constant expression">;
 def note_constexpr_access_inactive_union_member : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|"
-  "construction of subobject of|destruction of|read of}0 "
+  "%sub{access_kind_subobject}0 "
   "member %1 of union with %select{active member %3|no active member}2 "
   "is not allowed in a constant expression">;
 def note_constexpr_union_member_change_during_init : Note<
   "assignment would change active union member during the initialization of "
   "a different member of the same union">;
 def note_constexpr_access_static_temporary : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|reconstruction of|"
-  "destruction of|read of|read of}0 temporary "
+  "%sub{access_kind}0 temporary "
   "is not allowed in a constant expression outside the expression that "
   "created the temporary">;
 def note_constexpr_access_unreadable_object : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of|read of}0 "
+  "%sub{access_kind}0 "
   "object '%1' whose value is not known">;
 def note_constexpr_access_deleted_object : Note<
-  "%select{read of|read of|assignment to|increment of|decrement of|"
-  "member call on|dynamic_cast of|typeid applied to|construction of|"
-  "destruction of|read of}0 "
+  "%sub{access_kind}0 "
   "heap allocated object that has been deleted">;
 def note_constexpr_modify_global : Note<
   "a constant expression cannot modify an object that is visible outside "
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index b691e7109f9e69..6f36ef927f58d4 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6926,7 +6926,7 @@ ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
   // Bail out early if calling a builtin with custom type checking.
   if (BuiltinID && Context.BuiltinInfo.hasCustomTypechecking(BuiltinID)) {
     ExprResult E = CheckBuiltinFunctionCall(FDecl, BuiltinID, TheCall);
-    if (!E.isInvalid() && Context.BuiltinInfo.isImmediate(BuiltinID))
+    if (Context.BuiltinInfo.isImmediate(BuiltinID))
       E = CheckForImmediateInvocation(E, FDecl);
     return E;
   }
@@ -17707,7 +17707,8 @@ HandleImmediateInvocations(Sema &SemaRef,
         (SemaRef.inTemplateInstantiation() && !ImmediateEscalating)) {
       SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address)
           << ND << isa<CXXRecordDecl>(ND) << FD->isConsteval();
-      SemaRef.Diag(ND->getLocation(), diag::note_declared_at);
+      if (!FD->getBuiltinID())
+        SemaRef.Diag(ND->getLocation(), diag::note_declared_at);
       if (auto Context =
               SemaRef.InnermostDeclarationWithDelayedImmediateInvocations()) {
         SemaRef.Diag(Context->Loc, diag::note_invalid_consteval_initializer)
diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index c6190aff84abd9..e37fe4e1d00839 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -150,9 +150,27 @@ static_assert(test_volatile());
 constexpr bool self = __builtin_is_within_lifetime(&self);
 constexpr int external{};
 static_assert(__builtin_is_within_lifetime(&external));
-bool not_constexpr() {
-    return __builtin_is_within_lifetime(&external);
+void not_constexpr() {
+  __builtin_is_within_lifetime(&external);
 }
+void invalid_args() {
+  __builtin_is_within_lifetime(static_cast<int*>(nullptr));
+// expected-error at -1 {{'__builtin_is_within_lifetime' cannot be called with a null pointer}}
+// expected-error at -2 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+  __builtin_is_within_lifetime(0);
+// expected-error at -1 {{non-pointer argument to '__builtin_is_within_lifetime' is not allowed}}
+// expected-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime();
+// expected-error at -1 {{too few arguments to function call, expected 1, have 0}}
+// expected-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime(1, 2);
+// expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+// expected-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime(&external, &external);
+// expected-error at -1 {{too many arguments to function call, expected 1, have 2}}
+// expected-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+}
+
 constexpr struct {
   union {
     int i;
diff --git a/clang/test/SemaCXX/consteval-builtin.cpp b/clang/test/SemaCXX/consteval-builtin.cpp
index c8f89fc7c892c7..23195d01f9f974 100644
--- a/clang/test/SemaCXX/consteval-builtin.cpp
+++ b/clang/test/SemaCXX/consteval-builtin.cpp
@@ -58,4 +58,18 @@ constexpr void f3() {
   __builtin_is_within_lifetime(&i1);
 }
 void(&fp3)() = f3;
+
+constexpr void f4() {
+  &__builtin_is_within_lifetime;
+// cxx20-cxx26-error at -1 {{builtin functions must be directly called}}
+// cxx20-cxx26-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  __builtin_is_within_lifetime();
+// cxx20-cxx26-error at -1 {{too few arguments to function call, expected 1, have 0}}
+// cxx20-cxx26-error at -2 {{cannot take address of consteval function '__builtin_is_within_lifetime' outside of an immediate invocation}}
+  int* not_constexpr;
+  __builtin_is_within_lifetime(not_constexpr);
+// cxx20-cxx26-error at -1 {{call to consteval function '__builtin_is_within_lifetime' is not a constant expression}}
+//   cxx20-cxx26-note at -2 {{read of non-constexpr variable 'not_constexpr' is not allowed in a constant expression}}
+//   cxx20-cxx26-note at -4 {{declared here}}
+}
 #endif

>From 43470561ea71a402b814c3758e786a0cf1504d1f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 12 Aug 2024 21:15:14 +0100
Subject: [PATCH 10/11] Error on VLAs

---
 clang/lib/Sema/SemaChecking.cpp               | 10 ++++++-
 .../SemaCXX/builtin-is-within-lifetime.cpp    | 28 +++++++++++++++++--
 2 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 0126bd200dc45a..d4831a7f53fa82 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1862,7 +1862,15 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
   // std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine)
   // However, `std::is_within_lifetime` will only take pointer types (allow
   // non-const qualified too)
-  if (!ParamTy->isPointerType()) {
+  if (const auto *PT = ParamTy->getAs<PointerType>()) {
+    // Disallow VLAs too since those shouldn't be able to
+    // be a template parameter for `std::is_within_lifetime`
+    if (PT->getPointeeType()->isVariableArrayType()) {
+      S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_vla_unsupported)
+          << 1 << "__builtin_is_within_lifetime";
+      return ExprError();
+    }
+  } else {
     S.Diag(TheCall->getArg(0)->getExprLoc(),
            diag::err_builtin_is_within_lifetime_invalid_arg);
     return ExprError();
diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index e37fe4e1d00839..a3be78e99d6ae1 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -1,6 +1,6 @@
-// RUN: %clang_cc1 -std=c++20 -Wno-unused %s -verify=expected,cxx20
-// RUN: %clang_cc1 -std=c++23 -Wno-unused %s -verify=expected
-// RUN: %clang_cc1 -std=c++2c -Wno-unused %s -verify=expected
+// RUN: %clang_cc1 -std=c++20 -Wno-unused %s -verify=expected,cxx20 -Wno-vla-cxx-extension
+// RUN: %clang_cc1 -std=c++23 -Wno-unused %s -verify=expected,sincecxx23 -Wno-vla-cxx-extension
+// RUN: %clang_cc1 -std=c++2c -Wno-unused %s -verify=expected,sincecxx23 -Wno-vla-cxx-extension
 
 inline void* operator new(__SIZE_TYPE__, void* p) noexcept { return p; }
 namespace std {
@@ -270,3 +270,25 @@ constexpr OptBool engaged(true);
 static_assert(!disengaged.has_value());
 static_assert(engaged.has_value());
 static_assert(*engaged);
+
+namespace vlas {
+
+consteval bool f(int n) {
+  int vla[n]; // cxx20-error {{variable of non-literal type}}
+  return __builtin_is_within_lifetime(static_cast<void*>(&vla));
+}
+static_assert(f(1));
+
+consteval bool fail(int n) {
+  int vla[n]; // cxx20-error {{variable of non-literal type}}
+  return __builtin_is_within_lifetime(&vla); // expected-error {{variable length arrays are not supported in '__builtin_is_within_lifetime'}}
+}
+static_assert(fail(1)); // sincecxx23-error {{static assertion expression is not an integral constant expression}}
+
+consteval bool variably_modified(int n) {
+  int(* p)[n];
+  return __builtin_is_within_lifetime(&p);
+}
+static_assert(variably_modified(1));
+
+} // namespace vlas

>From 82178d2f0e6bf040647af214eafd37e8ecff9980 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 12 Aug 2024 21:44:04 +0100
Subject: [PATCH 11/11] Add some more tests

---
 clang/lib/Sema/SemaChecking.cpp               |  5 +-
 .../SemaCXX/builtin-is-within-lifetime.cpp    | 51 ++++++++++++++++++-
 2 files changed, 52 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index d4831a7f53fa82..1d2fa342429e2c 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -1845,7 +1845,7 @@ static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
 }
 
 static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
-  if (checkArgCount(S, TheCall, 1))
+  if (S.checkArgCount(TheCall, 1))
     return ExprError();
 
   ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0));
@@ -1858,8 +1858,7 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
   // A call to this function is always ill-formed if the type is not a pointer
   // to an object type. There is no Mandates: to that effect, so we can only
   // issue an error if it is actually evaluated as part of a constant evaluation
-  // (e.g., `false ? true :
-  // std::is_within_lifetime(static_cast<void(*)()>(nullptr));` is fine)
+  // (e.g., `false ? true : std::is_within_lifetime<void()>(nullptr);` is fine)
   // However, `std::is_within_lifetime` will only take pointer types (allow
   // non-const qualified too)
   if (const auto *PT = ParamTy->getAs<PointerType>()) {
diff --git a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
index a3be78e99d6ae1..eeb321b06e0ef0 100644
--- a/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
+++ b/clang/test/SemaCXX/builtin-is-within-lifetime.cpp
@@ -195,19 +195,21 @@ static_assert(!x2.b);
 static_assert(!x2.c);
 
 struct X3 {
-  bool a, b, c, d;
+  bool a, b, c, d, e;
   consteval X3();
 };
 extern const X3 x3;
 consteval X3::X3() : a(__builtin_is_within_lifetime(&b)), b(false), c(__builtin_is_within_lifetime(&b)) {
   b = __builtin_is_within_lifetime(&b);
   d = __builtin_is_within_lifetime(&x3.c);
+  e = __builtin_is_within_lifetime(this);
 }
 constexpr X3 x3{};
 static_assert(!x3.a);
 static_assert(!x3.b);
 static_assert(!x3.c);
 static_assert(!x3.d);
+static_assert(!x3.e);
 
 constexpr int i = 2;
 static_assert(__builtin_is_within_lifetime(const_cast<int*>(&i)));
@@ -292,3 +294,50 @@ consteval bool variably_modified(int n) {
 static_assert(variably_modified(1));
 
 } // namespace vlas
+
+consteval bool arrays() {
+  int(*arrp)[2];
+  {
+    int arr[2];
+    arrp = &arr;
+    if (!__builtin_is_within_lifetime(&arr) || !__builtin_is_within_lifetime(&arr[0]) || !__builtin_is_within_lifetime(&arr[1]))
+      return false;
+    std::destroy_at(&arr[0]);
+    if (!__builtin_is_within_lifetime(&arr) ||  __builtin_is_within_lifetime(&arr[0]) || !__builtin_is_within_lifetime(&arr[1]))
+      return false;
+    std::construct_at(&arr[0]);
+  }
+  return !__builtin_is_within_lifetime(arrp);
+}
+static_assert(arrays());
+
+struct NonTrivial {
+  constexpr NonTrivial() {}
+  constexpr NonTrivial(const NonTrivial&) {}
+  constexpr ~NonTrivial() {}
+};
+
+template<typename T>
+constexpr T& unmove(T&& temp) { return static_cast<T&>(temp); }
+
+consteval bool test_temporaries() {
+  static_assert(__builtin_is_within_lifetime(&unmove(0)));
+  static_assert(__builtin_is_within_lifetime(&unmove(NonTrivial{})));
+  if (!__builtin_is_within_lifetime(&unmove(0)))
+    return false;
+  if (!__builtin_is_within_lifetime(&unmove(NonTrivial{})))
+    return false;
+  return true;
+}
+static_assert(test_temporaries());
+
+constexpr const int& temp = 0;
+static_assert(__builtin_is_within_lifetime(&temp));
+
+template<typename T>
+constexpr T* test_dangling() {
+  T i;
+  return &i; // expected-warning 2 {{address of stack memory associated with local variable 'i' returned}}
+}
+static_assert(!__builtin_is_within_lifetime(test_dangling<int>())); // expected-note {{in instantiation of function template specialization}}
+static_assert(!__builtin_is_within_lifetime(test_dangling<int[1]>())); // expected-note {{in instantiation of function template specialization}}



More information about the cfe-commits mailing list