Skip to content

Commit 2a07509

Browse files
MitalAshokcor3ntin
andauthored
[Clang] Add __builtin_is_within_lifetime to implement P2641R4's std::is_within_lifetime (#91895)
[P2641R4](https://wg21.link/P2641R4) This new builtin function is declared `consteval`. Support for `-fexperimental-new-constant-interpreter` will be added in a later patch. --------- Co-authored-by: cor3ntin <[email protected]>
1 parent 485d191 commit 2a07509

File tree

11 files changed

+698
-7
lines changed

11 files changed

+698
-7
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ C++2c Feature Support
122122

123123
- Implemented `P2747R2 constexpr placement new <https://wg21.link/P2747R2>`_.
124124

125+
- Added the ``__builtin_is_within_lifetime`` builtin, which supports
126+
`P2641R4 Checking if a union alternative is active <https://wg21.link/p2641r4>`_
127+
125128
C++23 Feature Support
126129
^^^^^^^^^^^^^^^^^^^^^
127130
- Removed the restriction to literal types in constexpr functions in C++23 mode.

clang/include/clang/Basic/Builtins.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,12 @@ def IsConstantEvaluated : LangBuiltin<"CXX_LANG"> {
934934
let Prototype = "bool()";
935935
}
936936

937+
def IsWithinLifetime : LangBuiltin<"CXX_LANG"> {
938+
let Spellings = ["__builtin_is_within_lifetime"];
939+
let Attributes = [NoThrow, CustomTypeChecking, Consteval];
940+
let Prototype = "bool(void*)";
941+
}
942+
937943
// GCC exception builtins
938944
def EHReturn : Builtin {
939945
let Spellings = ["__builtin_eh_return"];

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ def note_constexpr_this : Note<
167167
def access_kind : TextSubstitution<
168168
"%select{read of|read of|assignment to|increment of|decrement of|"
169169
"member call on|dynamic_cast of|typeid applied to|construction of|"
170-
"destruction of}0">;
170+
"destruction of|read of}0">;
171171
def access_kind_subobject : TextSubstitution<
172172
"%select{read of|read of|assignment to|increment of|decrement of|"
173173
"member call on|dynamic_cast of|typeid applied to|"
174-
"construction of subobject of|destruction of}0">;
174+
"construction of subobject of|destruction of|read of}0">;
175175
def access_kind_volatile : TextSubstitution<
176176
"%select{read of|read of|assignment to|increment of|decrement of|"
177-
"<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>}0">;
177+
"<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>|<ERROR>}0">;
178178
def note_constexpr_lifetime_ended : Note<
179179
"%sub{access_kind}0 %select{temporary|variable}1 whose "
180180
"%plural{8:storage duration|:lifetime}0 has ended">;
@@ -407,6 +407,12 @@ def warn_is_constant_evaluated_always_true_constexpr : Warning<
407407
"'%0' will always evaluate to 'true' in a manifestly constant-evaluated expression">,
408408
InGroup<DiagGroup<"constant-evaluated">>;
409409

410+
def err_invalid_is_within_lifetime : Note<
411+
"'%0' cannot be called with "
412+
"%select{a null pointer|a one-past-the-end pointer|"
413+
"a pointer to an object whose lifetime has not yet begun}1"
414+
>;
415+
410416
// inline asm related.
411417
let CategoryName = "Inline Assembly Issue" in {
412418
def err_asm_invalid_escape : Error<

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12186,6 +12186,10 @@ def err_builtin_launder_invalid_arg : Error<
1218612186
"%select{non-pointer|function pointer|void pointer}0 argument to "
1218712187
"'__builtin_launder' is not allowed">;
1218812188

12189+
def err_builtin_is_within_lifetime_invalid_arg : Error<
12190+
"%select{non-|function }0pointer argument to '__builtin_is_within_lifetime' "
12191+
"is not allowed">;
12192+
1218912193
def err_builtin_invalid_arg_type: Error <
1219012194
"%ordinal0 argument must be "
1219112195
"%select{a vector, integer or floating point type|a matrix|"

clang/lib/AST/ByteCode/State.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ enum AccessKinds {
3434
AK_TypeId,
3535
AK_Construct,
3636
AK_Destroy,
37+
AK_IsWithinLifetime,
3738
};
3839

3940
/// The order of this enum is important for diagnostics.

clang/lib/AST/ExprConstant.cpp

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,7 +1522,8 @@ CallStackFrame::~CallStackFrame() {
15221522
}
15231523

15241524
static bool isRead(AccessKinds AK) {
1525-
return AK == AK_Read || AK == AK_ReadObjectRepresentation;
1525+
return AK == AK_Read || AK == AK_ReadObjectRepresentation ||
1526+
AK == AK_IsWithinLifetime;
15261527
}
15271528

15281529
static bool isModification(AccessKinds AK) {
@@ -1532,6 +1533,7 @@ static bool isModification(AccessKinds AK) {
15321533
case AK_MemberCall:
15331534
case AK_DynamicCast:
15341535
case AK_TypeId:
1536+
case AK_IsWithinLifetime:
15351537
return false;
15361538
case AK_Assign:
15371539
case AK_Increment:
@@ -1549,7 +1551,8 @@ static bool isAnyAccess(AccessKinds AK) {
15491551

15501552
/// Is this an access per the C++ definition?
15511553
static bool isFormalAccess(AccessKinds AK) {
1552-
return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy;
1554+
return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy &&
1555+
AK != AK_IsWithinLifetime;
15531556
}
15541557

15551558
/// Is this kind of axcess valid on an indeterminate object value?
@@ -1561,6 +1564,7 @@ static bool isValidIndeterminateAccess(AccessKinds AK) {
15611564
// These need the object's value.
15621565
return false;
15631566

1567+
case AK_IsWithinLifetime:
15641568
case AK_ReadObjectRepresentation:
15651569
case AK_Assign:
15661570
case AK_Construct:
@@ -3707,7 +3711,8 @@ struct CompleteObject {
37073711
// In C++14 onwards, it is permitted to read a mutable member whose
37083712
// lifetime began within the evaluation.
37093713
// FIXME: Should we also allow this in C++11?
3710-
if (!Info.getLangOpts().CPlusPlus14)
3714+
if (!Info.getLangOpts().CPlusPlus14 &&
3715+
AK != AccessKinds::AK_IsWithinLifetime)
37113716
return false;
37123717
return lifetimeStartedInEvaluation(Info, Base, /*MutableSubobject*/true);
37133718
}
@@ -3760,6 +3765,12 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
37603765
if ((O->isAbsent() && !(handler.AccessKind == AK_Construct && I == N)) ||
37613766
(O->isIndeterminate() &&
37623767
!isValidIndeterminateAccess(handler.AccessKind))) {
3768+
// Object has ended lifetime.
3769+
// If I is non-zero, some subobject (member or array element) of a
3770+
// complete object has ended its lifetime, so this is valid for
3771+
// IsWithinLifetime, resulting in false.
3772+
if (I != 0 && handler.AccessKind == AK_IsWithinLifetime)
3773+
return false;
37633774
if (!Info.checkingPotentialConstantExpression())
37643775
Info.FFDiag(E, diag::note_constexpr_access_uninit)
37653776
<< handler.AccessKind << O->isIndeterminate()
@@ -3927,6 +3938,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj,
39273938
// Placement new onto an inactive union member makes it active.
39283939
O->setUnion(Field, APValue());
39293940
} else {
3941+
// Pointer to/into inactive union member: Not within lifetime
3942+
if (handler.AccessKind == AK_IsWithinLifetime)
3943+
return false;
39303944
// FIXME: If O->getUnionValue() is absent, report that there's no
39313945
// active union member rather than reporting the prior active union
39323946
// member. We'll need to fix nullptr_t to not use APValue() as its
@@ -11684,6 +11698,9 @@ class IntExprEvaluator
1168411698

1168511699
bool ZeroInitialization(const Expr *E) { return Success(0, E); }
1168611700

11701+
friend std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &,
11702+
const CallExpr *);
11703+
1168711704
//===--------------------------------------------------------------------===//
1168811705
// Visitor Methods
1168911706
//===--------------------------------------------------------------------===//
@@ -12743,6 +12760,11 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
1274312760
return Success(Info.InConstantContext, E);
1274412761
}
1274512762

12763+
case Builtin::BI__builtin_is_within_lifetime:
12764+
if (auto result = EvaluateBuiltinIsWithinLifetime(*this, E))
12765+
return Success(*result, E);
12766+
return false;
12767+
1274612768
case Builtin::BI__builtin_ctz:
1274712769
case Builtin::BI__builtin_ctzl:
1274812770
case Builtin::BI__builtin_ctzll:
@@ -17322,3 +17344,84 @@ bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const {
1732217344
EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold);
1732317345
return EvaluateBuiltinStrLen(this, Result, Info);
1732417346
}
17347+
17348+
namespace {
17349+
struct IsWithinLifetimeHandler {
17350+
EvalInfo &Info;
17351+
static constexpr AccessKinds AccessKind = AccessKinds::AK_IsWithinLifetime;
17352+
using result_type = std::optional<bool>;
17353+
std::optional<bool> failed() { return std::nullopt; }
17354+
template <typename T>
17355+
std::optional<bool> found(T &Subobj, QualType SubobjType) {
17356+
return true;
17357+
}
17358+
};
17359+
17360+
std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE,
17361+
const CallExpr *E) {
17362+
EvalInfo &Info = IEE.Info;
17363+
// Sometimes this is called during some sorts of constant folding / early
17364+
// evaluation. These are meant for non-constant expressions and are not
17365+
// necessary since this consteval builtin will never be evaluated at runtime.
17366+
// Just fail to evaluate when not in a constant context.
17367+
if (!Info.InConstantContext)
17368+
return std::nullopt;
17369+
assert(E->getBuiltinCallee() == Builtin::BI__builtin_is_within_lifetime);
17370+
const Expr *Arg = E->getArg(0);
17371+
if (Arg->isValueDependent())
17372+
return std::nullopt;
17373+
LValue Val;
17374+
if (!EvaluatePointer(Arg, Val, Info))
17375+
return std::nullopt;
17376+
17377+
auto Error = [&](int Diag) {
17378+
bool CalledFromStd = false;
17379+
const auto *Callee = Info.CurrentCall->getCallee();
17380+
if (Callee && Callee->isInStdNamespace()) {
17381+
const IdentifierInfo *Identifier = Callee->getIdentifier();
17382+
CalledFromStd = Identifier && Identifier->isStr("is_within_lifetime");
17383+
}
17384+
Info.CCEDiag(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin()
17385+
: E->getExprLoc(),
17386+
diag::err_invalid_is_within_lifetime)
17387+
<< (CalledFromStd ? "std::is_within_lifetime"
17388+
: "__builtin_is_within_lifetime")
17389+
<< Diag;
17390+
return std::nullopt;
17391+
};
17392+
// C++2c [meta.const.eval]p4:
17393+
// During the evaluation of an expression E as a core constant expression, a
17394+
// call to this function is ill-formed unless p points to an object that is
17395+
// usable in constant expressions or whose complete object's lifetime began
17396+
// within E.
17397+
17398+
// Make sure it points to an object
17399+
// nullptr does not point to an object
17400+
if (Val.isNullPointer() || Val.getLValueBase().isNull())
17401+
return Error(0);
17402+
QualType T = Val.getLValueBase().getType();
17403+
assert(!T->isFunctionType() &&
17404+
"Pointers to functions should have been typed as function pointers "
17405+
"which would have been rejected earlier");
17406+
assert(T->isObjectType());
17407+
// Hypothetical array element is not an object
17408+
if (Val.getLValueDesignator().isOnePastTheEnd())
17409+
return Error(1);
17410+
assert(Val.getLValueDesignator().isValidSubobject() &&
17411+
"Unchecked case for valid subobject");
17412+
// All other ill-formed values should have failed EvaluatePointer, so the
17413+
// object should be a pointer to an object that is usable in a constant
17414+
// expression or whose complete lifetime began within the expression
17415+
CompleteObject CO =
17416+
findCompleteObject(Info, E, AccessKinds::AK_IsWithinLifetime, Val, T);
17417+
// The lifetime hasn't begun yet if we are still evaluating the
17418+
// initializer ([basic.life]p(1.2))
17419+
if (Info.EvaluatingDeclValue && CO.Value == Info.EvaluatingDeclValue)
17420+
return Error(2);
17421+
17422+
if (!CO)
17423+
return false;
17424+
IsWithinLifetimeHandler handler{Info};
17425+
return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler);
17426+
}
17427+
} // namespace

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2538,6 +2538,9 @@ static RValue EmitHipStdParUnsupportedBuiltin(CodeGenFunction *CGF,
25382538
RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
25392539
const CallExpr *E,
25402540
ReturnValueSlot ReturnValue) {
2541+
assert(!getContext().BuiltinInfo.isImmediate(BuiltinID) &&
2542+
"Should not codegen for consteval builtins");
2543+
25412544
const FunctionDecl *FD = GD.getDecl()->getAsFunction();
25422545
// See if we can constant fold this builtin. If so, don't emit it at all.
25432546
// TODO: Extend this handling to all builtin calls that we can constant-fold.

clang/lib/Sema/SemaChecking.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,6 +1844,44 @@ static ExprResult BuiltinLaunder(Sema &S, CallExpr *TheCall) {
18441844
return TheCall;
18451845
}
18461846

1847+
static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) {
1848+
if (S.checkArgCount(TheCall, 1))
1849+
return ExprError();
1850+
1851+
ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0));
1852+
if (Arg.isInvalid())
1853+
return ExprError();
1854+
QualType ParamTy = Arg.get()->getType();
1855+
TheCall->setArg(0, Arg.get());
1856+
TheCall->setType(S.Context.BoolTy);
1857+
1858+
// Only accept pointers to objects as arguments, which should have object
1859+
// pointer or void pointer types.
1860+
if (const auto *PT = ParamTy->getAs<PointerType>()) {
1861+
// LWG4138: Function pointer types not allowed
1862+
if (PT->getPointeeType()->isFunctionType()) {
1863+
S.Diag(TheCall->getArg(0)->getExprLoc(),
1864+
diag::err_builtin_is_within_lifetime_invalid_arg)
1865+
<< 1;
1866+
return ExprError();
1867+
}
1868+
// Disallow VLAs too since those shouldn't be able to
1869+
// be a template parameter for `std::is_within_lifetime`
1870+
if (PT->getPointeeType()->isVariableArrayType()) {
1871+
S.Diag(TheCall->getArg(0)->getExprLoc(), diag::err_vla_unsupported)
1872+
<< 1 << "__builtin_is_within_lifetime";
1873+
return ExprError();
1874+
}
1875+
} else {
1876+
S.Diag(TheCall->getArg(0)->getExprLoc(),
1877+
diag::err_builtin_is_within_lifetime_invalid_arg)
1878+
<< 0;
1879+
return ExprError();
1880+
}
1881+
1882+
return TheCall;
1883+
}
1884+
18471885
// Emit an error and return true if the current object format type is in the
18481886
// list of unsupported types.
18491887
static bool CheckBuiltinTargetNotInUnsupported(
@@ -2276,6 +2314,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
22762314
}
22772315
case Builtin::BI__builtin_launder:
22782316
return BuiltinLaunder(*this, TheCall);
2317+
case Builtin::BI__builtin_is_within_lifetime:
2318+
return BuiltinIsWithinLifetime(*this, TheCall);
22792319
case Builtin::BI__sync_fetch_and_add:
22802320
case Builtin::BI__sync_fetch_and_add_1:
22812321
case Builtin::BI__sync_fetch_and_add_2:

clang/lib/Sema/SemaExpr.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17622,7 +17622,8 @@ HandleImmediateInvocations(Sema &SemaRef,
1762217622
(SemaRef.inTemplateInstantiation() && !ImmediateEscalating)) {
1762317623
SemaRef.Diag(DR->getBeginLoc(), diag::err_invalid_consteval_take_address)
1762417624
<< ND << isa<CXXRecordDecl>(ND) << FD->isConsteval();
17625-
SemaRef.Diag(ND->getLocation(), diag::note_declared_at);
17625+
if (!FD->getBuiltinID())
17626+
SemaRef.Diag(ND->getLocation(), diag::note_declared_at);
1762617627
if (auto Context =
1762717628
SemaRef.InnermostDeclarationWithDelayedImmediateInvocations()) {
1762817629
SemaRef.Diag(Context->Loc, diag::note_invalid_consteval_initializer)

0 commit comments

Comments
 (0)