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

None

>From 56aed689dc5825fc5bacc6dfdff58ee0eaf71f82 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mi...@mitalashok.co.uk>
Date: Sun, 12 May 2024 19:48:24 +0100
Subject: [PATCH 1/2] [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 f955d21169556..e85ec5b2dca14 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 724747ec76d73..1196b9e15c10d 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 fb913034bd836..6b0a04585928a 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 53238d355ea09..1b558d70f9b48 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 bb4b116fd73ca..39aa32526d2b1 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 323c63e202883..31409a4d46a65 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@+2 {{cannot redeclare builtin function '__builtin_va_end'}}
 // expected-note@+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@+1 {{constexpr declaration of '__builtin_calloc' follows 
non-constexpr declaration}}
+constexpr void *__builtin_calloc(size_t, size_t);
+// expected-note@-1 {{previous declaration is here}}
 #endif

>From 253f58f0e34bc6dedbbfe17f68cfe9baa3a9146f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mi...@mitalashok.co.uk>
Date: Sun, 12 May 2024 18:42:49 +0100
Subject: [PATCH 2/2] [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 11982af3fa609..04660122565b8 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 a024f9b2a9f8c..7f8908e45663b 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 9e82130c93609..3f60b86544953 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 f1aa19e4409e1..f3b31d87dde9b 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 f1e8e3618f34f..65039a67c7de4 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 54789dde50691..1ca1d34c58b8a 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 0000000000000..fcc6303e051e6
--- /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@-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@-1 {{call to consteval function 
'__builtin_is_within_lifetime' is not a constant expression}}
+//   expected-note@-2 {{initializer of 'self' is unknown}}
+//   expected-note@-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@-1 {{static assertion expression is not an integral constant 
expression}}
+//   expected-note@-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@-1 {{call to consteval function 
'__builtin_is_within_lifetime' is not a constant expression}}
+// expected-error@-2 {{'__builtin_is_within_lifetime' cannot be called with a 
one-past-the-end pointer}}
+  __builtin_is_within_lifetime(arr + 2);
+// expected-error@-1 {{call to consteval function 
'__builtin_is_within_lifetime' is not a constant expression}}
+// expected-error@-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@-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);

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to