[clang] [Clang] Implement `__builtin_start_lifetime_as` for C++23 P2590R2 (PR #196502)
Yash Verma via cfe-commits
cfe-commits at lists.llvm.org
Fri May 8 03:02:40 PDT 2026
https://github.com/My-Bad-2 created https://github.com/llvm/llvm-project/pull/196502
This PR introduces `__builtin_start_lifetime_as`, the compiler frontend primitive required to implement C++23 `std::start_lifetime_as` and `std::start_lifetime_as_array` ([P2590R2](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2590r2.pdf)).
This builtin acts as a gatekeeper in the AST by validating standard compliance by enforcing implicit-lifetime type contraints, intercepting VLAs, and rejecting evaluation in `constexpr` contexts.
The implementation also provides a dual-path approach via an optional boolean flag. When provided, this flag instructs `Sema` to bypass the C++23 implicit-lifetime checks.
Why Reject VLAs? GCC 16 also rejects VLAs from `std::start_lifetime_as`. After a bit of research, it appears that because `std::start_lifetime_as` requires a fixed-size type where are VLAs have a runtime-determined size.
>From a13a3feb53ea6d5d5c333094239267e71b0663cd Mon Sep 17 00:00:00 2001
From: Yash Verma <yashverma056 at proton.me>
Date: Thu, 7 May 2026 13:22:51 -0400
Subject: [PATCH 1/3] Adds `bool isImplicitLifetimeType() const` to
`clang::Type` to map the C++20 [basic.types.general] rules to the AST.
This method serves as a common path to determine whether a type is an implicit-lifetime type.
---
clang/include/clang/AST/TypeBase.h | 3 ++
clang/lib/AST/Type.cpp | 59 ++++++++++++++++++++++++++++++
clang/lib/Sema/SemaTypeTraits.cpp | 43 +---------------------
3 files changed, 63 insertions(+), 42 deletions(-)
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index b2887bcc36246..6476d85d46984 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -2470,6 +2470,9 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
/// or QualType::getSingleStepDesugaredType(const ASTContext&).
QualType getLocallyUnqualifiedSingleStepDesugaredType() const;
+ /// Determine whether this type is an implicit-lifetime type.
+ bool isImplicitLifetimeType() const;
+
/// As an extension, we classify types as one of "sized" or "sizeless";
/// every type is one or the other. Standard types are all sized;
/// sizeless types are purely an extension.
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 06023fc088a32..ffb25b4f51350 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5914,3 +5914,62 @@ StringRef PredefinedSugarType::getName(Kind KD) {
}
llvm_unreachable("unexpected kind");
}
+
+bool Type::isImplicitLifetimeType() const {
+ // [basic.types.general] p9
+ // Scalar types, implicit-lifetime class types ([class.prop]), array types,
+ // and cv-qualified versions of these types are collectively called
+ // implicit-lifetime types.
+ QualType UnqualT = getCanonicalTypeUnqualified();
+
+ if (UnqualT->isScalarType())
+ return true;
+
+ // Arrays are implicit Lifetime types.
+ if (UnqualT->isArrayType() || UnqualT->isVectorType() ||
+ UnqualT->isExtVectorType())
+ return true;
+
+ const CXXRecordDecl *RD = UnqualT->getAsCXXRecordDecl();
+ if (!RD)
+ return false;
+
+ // [class.prop] p9
+ // A class S is an implicit-lifetime class if
+ // - it is an aggregate whose destructor is not user-provided or
+ // - it has atleast one trivial eligible constructor and a trivial,
+ // non-deleted destructor.
+
+ const CXXDestructorDecl *Dtor = RD->getDestructor();
+ if (UnqualT->isAggregateType() && (!Dtor || !Dtor->isUserProvided()))
+ return true;
+
+ // Type must have a trivial, non-deleted destructor
+ bool HasTrivialNonDeletedDtr =
+ RD->hasTrivialDestructor() && (!Dtor || !Dtor->isDeleted());
+ if (!HasTrivialNonDeletedDtr)
+ return false;
+
+ for (CXXConstructorDecl *Ctor : RD->ctors()) {
+ if (Ctor->isIneligibleOrNotSelected() || Ctor->isDeleted())
+ continue;
+
+ if (Ctor->isTrivial())
+ return true;
+ }
+
+ if (RD->needsImplicitDefaultConstructor() &&
+ RD->hasTrivialDefaultConstructor() &&
+ !RD->hasNonTrivialDefaultConstructor())
+ return true;
+
+ if (RD->needsImplicitCopyConstructor() && RD->hasTrivialCopyConstructor() &&
+ !RD->defaultedCopyConstructorIsDeleted())
+ return true;
+
+ if (RD->needsImplicitMoveConstructor() && RD->hasTrivialMoveConstructor() &&
+ !RD->defaultedMoveConstructorIsDeleted())
+ return true;
+
+ return false;
+}
\ No newline at end of file
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index a94a59e8add7b..7f575fdacf9ae 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -1070,48 +1070,7 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
DiagnoseAtomicInCXXTypeTrait(Self, TInfo,
tok::kw___builtin_is_implicit_lifetime);
- // [basic.types.general] p9
- // Scalar types, implicit-lifetime class types ([class.prop]),
- // array types, and cv-qualified versions of these types
- // are collectively called implicit-lifetime types.
- QualType UnqualT = T->getCanonicalTypeUnqualified();
- if (UnqualT->isScalarType())
- return true;
- if (UnqualT->isArrayType() || UnqualT->isVectorType())
- return true;
- const CXXRecordDecl *RD = UnqualT->getAsCXXRecordDecl();
- if (!RD)
- return false;
-
- // [class.prop] p9
- // A class S is an implicit-lifetime class if
- // - it is an aggregate whose destructor is not user-provided or
- // - it has at least one trivial eligible constructor and a trivial,
- // non-deleted destructor.
- const CXXDestructorDecl *Dtor = RD->getDestructor();
- if (UnqualT->isAggregateType() && (!Dtor || !Dtor->isUserProvided()))
- return true;
- bool HasTrivialNonDeletedDtr =
- RD->hasTrivialDestructor() && (!Dtor || !Dtor->isDeleted());
- if (!HasTrivialNonDeletedDtr)
- return false;
- for (CXXConstructorDecl *Ctr : RD->ctors()) {
- if (Ctr->isIneligibleOrNotSelected() || Ctr->isDeleted())
- continue;
- if (Ctr->isTrivial())
- return true;
- }
- if (RD->needsImplicitDefaultConstructor() &&
- RD->hasTrivialDefaultConstructor() &&
- !RD->hasNonTrivialDefaultConstructor())
- return true;
- if (RD->needsImplicitCopyConstructor() && RD->hasTrivialCopyConstructor() &&
- !RD->defaultedCopyConstructorIsDeleted())
- return true;
- if (RD->needsImplicitMoveConstructor() && RD->hasTrivialMoveConstructor() &&
- !RD->defaultedMoveConstructorIsDeleted())
- return true;
- return false;
+ return T->isImplicitLifetimeType();
}
case UTT_IsIntangibleType:
assert(Self.getLangOpts().HLSL && "intangible types are HLSL-only feature");
>From 70c37dcf81c65f0f71f5a641068afe21a4df7a78 Mon Sep 17 00:00:00 2001
From: Yash Verma <yashverma056 at proton.me>
Date: Fri, 8 May 2026 04:33:46 -0400
Subject: [PATCH 2/3] [Clang] Implement __builtin_start_lifetime_as instrinsic
This introduces the compiler frontend primitive required to implement C++23 `std::start_lifetime_as` and `std::start_lifetime_as_array`.
Key Changes:
- Builtins.td: Register `__builtin_start_lifetime_as`
- SemaChecking.cpp: Implement `Sema::BuiltinStartLifetimeAs` to validate pointer arguments and intercept VLAs.
- AST Validation: Reuses `isImplicitLifetimeType()` to enforce C++23 standard compliance on the target type.
- Dual-mode: Supports an optional secondary boolean argument to bypass the strict implicit-lifetime trait check.
- ExprConstant.cpp: Explicitly rejects compile-time evaluation of the intrinsic.
---
clang/include/clang/Basic/Builtins.td | 6 +
.../clang/Basic/DiagnosticSemaKinds.td | 9 ++
clang/include/clang/Sema/Sema.h | 6 +
clang/lib/AST/ExprConstant.cpp | 7 ++
clang/lib/Sema/SemaChecking.cpp | 77 ++++++++++++-
.../SemaCXX/builtin-start-lifetime-as.cpp | 108 ++++++++++++++++++
6 files changed, 212 insertions(+), 1 deletion(-)
create mode 100644 clang/test/SemaCXX/builtin-start-lifetime-as.cpp
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index 4a7eaeb3d353e..e387c990efdd6 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -5883,3 +5883,9 @@ def CountedByRef : Builtin {
let Attributes = [NoThrow, CustomTypeChecking];
let Prototype = "int(...)";
}
+
+def StartLifetimeAs : Builtin {
+ let Spellings = ["__builtin_start_lifetime_as"];
+ let Attributes = [NoThrow, CustomTypeChecking];
+ let Prototype = "void *(...)";
+}
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c69b2ce3648f8..a7bb79207c354 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -14238,4 +14238,13 @@ def err_cuda_device_kernel_launch_not_supported
def err_cuda_device_kernel_launch_require_rdc
: Error<"kernel launch from __device__ or __global__ function requires "
"relocatable device code (i.e. requires -fgpu-rdc)">;
+def err_start_lifetime_as_not_implicit : Error<
+ "type %0 is not an implicit-lifetime type; cannot start lifetime">;
+def err_builtin_start_lifetime_as_invalid_arg : Error<
+ "non-pointer argument to '__builtin_start_lifetime_as' is not allowed">;
+def note_constexpr_start_lifetime : Note<
+ "implicitly creating objects and dynamically altering lifetimes is not "
+ "supported during constant evalutaion">;
+def err_builtin_start_lifetime_as_vla : Error<
+ "variable length arrays are not supported in '__builtin_start_lifetime_as'">;
} // end of sema component.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 72beac7526dc5..c94d2db54e279 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2893,6 +2893,12 @@ class Sema final : public SemaBase {
bool BuiltinConstantArgShiftedByteOrXXFF(CallExpr *TheCall, unsigned ArgNum,
unsigned ArgBits);
+ /// BuiltinStartLifetimeAs - Ensures the argument is a valid pointer to a
+ /// complete type. In strict mode (default), it also enforces C++23
+ /// implicit-lifetime constraints. Returns true on error, false on success or
+ /// if type-dependent.
+ bool BuiltinStartLifetimeAs(CallExpr *TheCall);
+
/// Checks that a call expression's argument count is at least the desired
/// number. This is useful when doing custom type-checking on a variadic
/// function. Returns true on error.
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3f3a80f5b77a3..50a02031fb0b2 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10549,6 +10549,13 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
return ZeroInitialization(E);
}
+ case Builtin::BI__builtin_start_lifetime_as: {
+ // C++23 forbids evaluating lifetime-altering abstract machine magic inside
+ // a constant expression.
+ Info.FFDiag(E, diag::note_constexpr_start_lifetime);
+ return false;
+ }
+
case Builtin::BImemcpy:
case Builtin::BImemmove:
case Builtin::BIwmemcpy:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 4706fa5d3cde0..15043defc459a 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -41,6 +41,7 @@
#include "clang/AST/TypeLoc.h"
#include "clang/AST/UnresolvedSet.h"
#include "clang/Basic/AddressSpaces.h"
+#include "clang/Basic/Builtins.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/IdentifierTable.h"
@@ -3125,7 +3126,10 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
return BuiltinIsWithinLifetime(*this, TheCall);
case Builtin::BI__builtin_trivially_relocate:
return BuiltinTriviallyRelocate(*this, TheCall);
-
+ case Builtin::BI__builtin_start_lifetime_as:
+ if (BuiltinStartLifetimeAs(TheCall))
+ return ExprError();
+ break;
case Builtin::BI__sync_fetch_and_add:
case Builtin::BI__sync_fetch_and_add_1:
case Builtin::BI__sync_fetch_and_add_2:
@@ -17298,3 +17302,74 @@ void Sema::CheckTCBEnforcement(const SourceLocation CallExprLoc,
}
}
}
+
+bool Sema::BuiltinStartLifetimeAs(CallExpr *Call) {
+ // Argument count: 1 (Strict) or 2 (Pointer, StrictnessFlag)
+ if (checkArgCountRange(Call, 1, 2))
+ return true;
+
+ Expr *PtrArg = Call->getArg(0);
+ QualType PtrType = PtrArg->getType();
+
+ const PointerType *PT = PtrType->getAs<PointerType>();
+ if (!PT) {
+ Diag(PtrArg->getExprLoc(), diag::err_builtin_start_lifetime_as_invalid_arg)
+ << PtrArg->getSourceRange();
+ return true;
+ }
+
+ QualType PointeeType = PT->getPointeeType();
+
+ // Variable Length Arrays are "complete types" in clang, but they lack a
+ // static size. GCC doesn't support variable-sized arrays in
+ // `std::start_lifetime_as`.
+ if (PointeeType->isVariableArrayType()) {
+ // We cannot use `err_vla_unsupported` because of some FormatDiagnostic
+ // issue
+ Diag(PtrArg->getExprLoc(), diag::err_builtin_start_lifetime_as_vla)
+ << PtrArg->getSourceRange();
+ return true;
+ }
+
+ if (Call->isTypeDependent() || Call->isValueDependent())
+ return false;
+
+ // Reject void and function pointers immediately.
+ if (PointeeType->isVoidType() || PointeeType->isFunctionType()) {
+ Diag(PtrArg->getExprLoc(), diag::err_start_lifetime_as_not_implicit)
+ << PointeeType << PtrArg->getSourceRange();
+ return true;
+ }
+
+ // Ensure the type is fully defined
+ if (RequireCompleteType(PtrArg->getExprLoc(), PointeeType,
+ diag::err_incomplete_type))
+ return true;
+
+ bool IsStrict = true;
+ if (Call->getNumArgs() == 2) {
+ Expr *StrictArg = Call->getArg(1);
+
+ std::optional<llvm::APSInt> OptStrict =
+ StrictArg->getIntegerConstantExpr(Context);
+
+ if (!OptStrict) {
+ Diag(StrictArg->getExprLoc(), diag::err_expr_not_ice)
+ << 0 << StrictArg->getSourceRange();
+ return true;
+ }
+
+ IsStrict = OptStrict->getBoolValue();
+ }
+
+ // Note: If the user called `start_lifetime_as_array<int>`, the PointeeType is
+ // simply `int` so no need to unwrap the array.
+ if (IsStrict && !PointeeType->isImplicitLifetimeType()) {
+ Diag(PtrArg->getExprLoc(), diag::err_start_lifetime_as_not_implicit)
+ << PointeeType << PtrArg->getSourceRange();
+ return true;
+ }
+
+ Call->setType(PtrType);
+ return false;
+}
diff --git a/clang/test/SemaCXX/builtin-start-lifetime-as.cpp b/clang/test/SemaCXX/builtin-start-lifetime-as.cpp
new file mode 100644
index 0000000000000..82286e60914aa
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-start-lifetime-as.cpp
@@ -0,0 +1,108 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+
+namespace InvalidArgs {
+ void test_non_pointers(int x) {
+ __builtin_start_lifetime_as(x); // expected-error {{non-pointer argument to '__builtin_start_lifetime_as' is not allowed}}
+ __builtin_start_lifetime_as(nullptr); // expected-error {{non-pointer argument to '__builtin_start_lifetime_as' is not allowed}}
+ }
+
+ void test_void_and_func(void *p, void (*f)()) {
+ __builtin_start_lifetime_as(p); // expected-error {{type 'void' is not an implicit-lifetime type; cannot start lifetime}}
+ __builtin_start_lifetime_as(f); // expected-error {{type 'void ()' is not an implicit-lifetime type; cannot start lifetime}}
+ }
+
+ struct Incomplete; // expected-note {{forward declaration of 'InvalidArgs::Incomplete'}}
+ void test_incomplete(Incomplete *p) {
+ __builtin_start_lifetime_as(p); // expected-error {{incomplete type 'Incomplete' where a complete type is required}}
+ }
+
+ void test_vla(int n) { // expected-note {{declared here}}
+ int vla[n]; // expected-warning {{variable length arrays in C++ are a Clang extension}} \
+ // expected-note {{function parameter 'n' with unknown value cannot be used in a constant expression}}
+ __builtin_start_lifetime_as(&vla); // expected-error {{variable length arrays are not supported in '__builtin_start_lifetime_as'}}
+ }
+} // namespace InvalidArgs
+
+namespace ImplicitLifetimeRules {
+ // Valid types
+ struct Trivial { int x; int y; };
+ struct TrivialArray { int arr[5]; };
+ union TrivialUnion { int a; float b; };
+
+ struct AggregateNoDtor { int a; ~AggregateNoDtor() = default; };
+
+ // Invalid types
+ struct UserDtor { ~UserDtor() {} };
+ struct UserCopy { UserCopy(const UserCopy&); };
+ struct VirtualBase { virtual void f(); };
+
+ void test_valid_types(void* p) {
+ __builtin_start_lifetime_as((int*)p);
+ __builtin_start_lifetime_as((Trivial*)p);
+ __builtin_start_lifetime_as((TrivialArray*)p);
+ __builtin_start_lifetime_as((TrivialUnion*)p);
+ __builtin_start_lifetime_as((AggregateNoDtor*)p);
+
+ // Arrays of implicit-lifetime types are implicitly valid
+ __builtin_start_lifetime_as((int(*)[5])p);
+
+ // Arrays of non-implicit-lifetime types are also valid under C++23
+ __builtin_start_lifetime_as((UserDtor(*)[5])p);
+ }
+
+ void test_invalid_types(void *p) {
+ __builtin_start_lifetime_as((UserDtor*)p); // expected-error {{type 'UserDtor' is not an implicit-lifetime type; cannot start lifetime}}
+ __builtin_start_lifetime_as((UserCopy*)p); // expected-error {{type 'UserCopy' is not an implicit-lifetime type; cannot start lifetime}}
+ __builtin_start_lifetime_as((VirtualBase*)p); // expected-error {{type 'VirtualBase' is not an implicit-lifetime type; cannot start lifetime}}
+ }
+} // namespace ImplicitLifetimeRules
+
+namespace StrictnessFlag {
+ struct NonTrivial { ~NonTrivial() {} };
+
+ void test_flag(NonTrivial* p, int runtime_flag) {
+ // defaults to strict
+ __builtin_start_lifetime_as(p); // expected-error {{type 'NonTrivial' is not an implicit-lifetime type}}
+
+ // explicit strict
+ __builtin_start_lifetime_as(p, true); // expected-error {{type 'NonTrivial' is not an implicit-lifetime type}}
+
+ // bypasses the implicit-lifetime check
+ __builtin_start_lifetime_as(p, false);
+
+ // Flag must be an ICE (Integer Constant Expression)
+ __builtin_start_lifetime_as(p, runtime_flag); // expected-error {{expression is not an integer constant expression}}
+ }
+} // namespace StrictnessFlag
+
+namespace Templates {
+ template <typename T>
+ T* test_dependent_type(void *p) {
+ // Should defer evaluation until instantiation
+ return __builtin_start_lifetime_as((T*)p); // expected-error {{type 'Templates::std::string' is not an implicit-lifetime type}}
+ }
+
+ namespace std { struct string { ~string() {} }; }
+
+ void instantiate(void *p) {
+ test_dependent_type<int>(p);
+ test_dependent_type<std::string>(p); // expected-note {{in instantiation of function template specialization 'Templates::test_dependent_type<Templates::std::string>' requested here}}
+ }
+} // namespace Templates
+
+namespace ConstexprEvaluation {
+ struct Trivial { int x; };
+
+ constexpr Trivial* test_constexpr_builtin(Trivial* p) {
+ return __builtin_start_lifetime_as(p); // expected-note {{subexpression not valid in a constant expression}}
+ }
+
+ constexpr bool test_eval() {
+ Trivial t{0};
+ Trivial* p = test_constexpr_builtin(&t); // expected-note {{in call to 'test_constexpr_builtin(&t)'}}
+ return p != nullptr;
+ }
+
+ constexpr bool b = test_eval(); // expected-error {{constexpr variable 'b' must be initialized by a constant expression}} \
+ // expected-note {{in call to 'test_eval()'}}
+}
>From 1de2a8a4777d0d4ad893260e73e0390971a5ff7b Mon Sep 17 00:00:00 2001
From: Yash Verma <yashverma056 at proton.me>
Date: Fri, 8 May 2026 05:41:54 -0400
Subject: [PATCH 3/3] [Clang][CodeGen] Implement IR generation for
__builtin_start_lifetime_as
This path implements the CodeGen lowering for the C++23 `std::start_lifetime_as` intrinsic.
Key Changes:
- CGBuiltin.cpp: Route `BI__builtin_start_lifetime_as` to share the existing `__builtin_launder` optimization barrier path.
---
clang/lib/CodeGen/CGBuiltin.cpp | 2 +
.../CodeGenCXX/builtin-start-lifetime-as.cpp | 42 +++++++
.../SemaCXX/builtin-start-lifetime-as.cpp | 114 +++++++++---------
3 files changed, 101 insertions(+), 57 deletions(-)
create mode 100644 clang/test/CodeGenCXX/builtin-start-lifetime-as.cpp
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 67de2a34f44ea..5d05ecad6a6c8 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -26,6 +26,7 @@
#include "TargetInfo.h"
#include "clang/AST/OSLog.h"
#include "clang/AST/StmtVisitor.h"
+#include "clang/Basic/Builtins.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/IR/InlineAsm.h"
@@ -5180,6 +5181,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
return RValue::get(nullptr);
}
+ case Builtin::BI__builtin_start_lifetime_as:
case Builtin::BI__builtin_launder: {
const Expr *Arg = E->getArg(0);
QualType ArgTy = Arg->getType()->getPointeeType();
diff --git a/clang/test/CodeGenCXX/builtin-start-lifetime-as.cpp b/clang/test/CodeGenCXX/builtin-start-lifetime-as.cpp
new file mode 100644
index 0000000000000..264ae6e4d4e5f
--- /dev/null
+++ b/clang/test/CodeGenCXX/builtin-start-lifetime-as.cpp
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -std=c++23 -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+
+struct Trivial { int x; float y; };
+
+// CHECK-LABEL: define {{.*}}@_Z12test_trivialPv(
+// CHECK-NOT: call ptr @llvm.launder.invariant.group
+// CHECK: ret ptr
+Trivial* test_trivial(void* p) {
+ return __builtin_start_lifetime_as((Trivial*)p);
+}
+
+// CHECK-LABEL: define {{.*}}@_Z18test_array_trivialPv(
+// CHECK-NOT: call ptr @llvm.launder.invariant.group
+// CHECK: ret ptr
+int (*test_array_trivial(void* p))[5] {
+ return __builtin_start_lifetime_as((int(*)[5])p);
+}
+
+struct WithConst { const int x; };
+struct WithRef { int& x; };
+
+// CHECK-LABEL: define {{.*}}@_Z10test_constPv(
+// CHECK-NOT: call ptr @llvm.launder.invariant.group
+// CHECK: ret ptr
+WithConst* test_const(void* p) {
+ return __builtin_start_lifetime_as((WithConst*)p);
+}
+
+// CHECK-LABEL: define {{.*}}@_Z8test_refPv(
+// CHECK-NOT: call ptr @llvm.launder.invariant.group
+// CHECK: ret ptr
+WithRef* test_ref(void* p) {
+ return __builtin_start_lifetime_as((WithRef*)p);
+}
+
+// CHECK-LABEL: define {{.*}}@_Z20test_strict_flag_laxPv(
+// CHECK-NOT: call ptr @llvm.launder.invariant.group
+// CHECK: ret ptr
+WithConst* test_strict_flag_lax(void* p) {
+ // Pass 'false' to lax mode; CodeGen cleanly drops the second argument
+ return __builtin_start_lifetime_as((WithConst*)p, false);
+}
diff --git a/clang/test/SemaCXX/builtin-start-lifetime-as.cpp b/clang/test/SemaCXX/builtin-start-lifetime-as.cpp
index 82286e60914aa..fec29a1945b67 100644
--- a/clang/test/SemaCXX/builtin-start-lifetime-as.cpp
+++ b/clang/test/SemaCXX/builtin-start-lifetime-as.cpp
@@ -1,56 +1,56 @@
// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
namespace InvalidArgs {
- void test_non_pointers(int x) {
- __builtin_start_lifetime_as(x); // expected-error {{non-pointer argument to '__builtin_start_lifetime_as' is not allowed}}
- __builtin_start_lifetime_as(nullptr); // expected-error {{non-pointer argument to '__builtin_start_lifetime_as' is not allowed}}
- }
-
- void test_void_and_func(void *p, void (*f)()) {
- __builtin_start_lifetime_as(p); // expected-error {{type 'void' is not an implicit-lifetime type; cannot start lifetime}}
- __builtin_start_lifetime_as(f); // expected-error {{type 'void ()' is not an implicit-lifetime type; cannot start lifetime}}
- }
-
- struct Incomplete; // expected-note {{forward declaration of 'InvalidArgs::Incomplete'}}
- void test_incomplete(Incomplete *p) {
- __builtin_start_lifetime_as(p); // expected-error {{incomplete type 'Incomplete' where a complete type is required}}
- }
-
- void test_vla(int n) { // expected-note {{declared here}}
- int vla[n]; // expected-warning {{variable length arrays in C++ are a Clang extension}} \
- // expected-note {{function parameter 'n' with unknown value cannot be used in a constant expression}}
- __builtin_start_lifetime_as(&vla); // expected-error {{variable length arrays are not supported in '__builtin_start_lifetime_as'}}
- }
-} // namespace InvalidArgs
-
-namespace ImplicitLifetimeRules {
- // Valid types
- struct Trivial { int x; int y; };
- struct TrivialArray { int arr[5]; };
- union TrivialUnion { int a; float b; };
-
- struct AggregateNoDtor { int a; ~AggregateNoDtor() = default; };
+ void test_non_pointers(int x) {
+ __builtin_start_lifetime_as(x); // expected-error {{non-pointer argument to '__builtin_start_lifetime_as' is not allowed}}
+ __builtin_start_lifetime_as(nullptr); // expected-error {{non-pointer argument to '__builtin_start_lifetime_as' is not allowed}}
+ }
- // Invalid types
- struct UserDtor { ~UserDtor() {} };
- struct UserCopy { UserCopy(const UserCopy&); };
- struct VirtualBase { virtual void f(); };
+ void test_void_and_func(void *p, void (*f)()) {
+ __builtin_start_lifetime_as(p); // expected-error {{type 'void' is not an implicit-lifetime type; cannot start lifetime}}
+ __builtin_start_lifetime_as(f); // expected-error {{type 'void ()' is not an implicit-lifetime type; cannot start lifetime}}
+ }
- void test_valid_types(void* p) {
- __builtin_start_lifetime_as((int*)p);
- __builtin_start_lifetime_as((Trivial*)p);
- __builtin_start_lifetime_as((TrivialArray*)p);
- __builtin_start_lifetime_as((TrivialUnion*)p);
- __builtin_start_lifetime_as((AggregateNoDtor*)p);
+ struct Incomplete; // expected-note {{forward declaration of 'InvalidArgs::Incomplete'}}
+ void test_incomplete(Incomplete *p) {
+ __builtin_start_lifetime_as(p); // expected-error {{incomplete type 'Incomplete' where a complete type is required}}
+ }
- // Arrays of implicit-lifetime types are implicitly valid
- __builtin_start_lifetime_as((int(*)[5])p);
+ void test_vla(int n) { // expected-note {{declared here}}
+ int vla[n]; // expected-warning {{variable length arrays in C++ are a Clang extension}} \
+ // expected-note {{function parameter 'n' with unknown value cannot be used in a constant expression}}
+ __builtin_start_lifetime_as(&vla); // expected-error {{variable length arrays are not supported in '__builtin_start_lifetime_as'}}
+ }
+} // namespace InvalidArgs
- // Arrays of non-implicit-lifetime types are also valid under C++23
- __builtin_start_lifetime_as((UserDtor(*)[5])p);
- }
+namespace ImplicitLifetimeRules {
+ // Valid types
+ struct Trivial { int x; int y; };
+ struct TrivialArray { int arr[5]; };
+ union TrivialUnion { int a; float b; };
+
+ struct AggregateNoDtor { int a; ~AggregateNoDtor() = default; };
+
+ // Invalid types
+ struct UserDtor { ~UserDtor() {} };
+ struct UserCopy { UserCopy(const UserCopy&); };
+ struct VirtualBase { virtual void f(); };
+
+ void test_valid_types(void* p) {
+ __builtin_start_lifetime_as((int*)p);
+ __builtin_start_lifetime_as((Trivial*)p);
+ __builtin_start_lifetime_as((TrivialArray*)p);
+ __builtin_start_lifetime_as((TrivialUnion*)p);
+ __builtin_start_lifetime_as((AggregateNoDtor*)p);
+
+ // Arrays of implicit-lifetime types are implicitly valid
+ __builtin_start_lifetime_as((int(*)[5])p);
+
+ // Arrays of non-implicit-lifetime types are also valid under C++23
+ __builtin_start_lifetime_as((UserDtor(*)[5])p);
+ }
- void test_invalid_types(void *p) {
+ void test_invalid_types(void *p) {
__builtin_start_lifetime_as((UserDtor*)p); // expected-error {{type 'UserDtor' is not an implicit-lifetime type; cannot start lifetime}}
__builtin_start_lifetime_as((UserCopy*)p); // expected-error {{type 'UserCopy' is not an implicit-lifetime type; cannot start lifetime}}
__builtin_start_lifetime_as((VirtualBase*)p); // expected-error {{type 'VirtualBase' is not an implicit-lifetime type; cannot start lifetime}}
@@ -58,21 +58,21 @@ namespace ImplicitLifetimeRules {
} // namespace ImplicitLifetimeRules
namespace StrictnessFlag {
- struct NonTrivial { ~NonTrivial() {} };
+ struct NonTrivial { ~NonTrivial() {} };
- void test_flag(NonTrivial* p, int runtime_flag) {
- // defaults to strict
- __builtin_start_lifetime_as(p); // expected-error {{type 'NonTrivial' is not an implicit-lifetime type}}
+ void test_flag(NonTrivial* p, int runtime_flag) {
+ // defaults to strict
+ __builtin_start_lifetime_as(p); // expected-error {{type 'NonTrivial' is not an implicit-lifetime type}}
- // explicit strict
- __builtin_start_lifetime_as(p, true); // expected-error {{type 'NonTrivial' is not an implicit-lifetime type}}
+ // explicit strict
+ __builtin_start_lifetime_as(p, true); // expected-error {{type 'NonTrivial' is not an implicit-lifetime type}}
- // bypasses the implicit-lifetime check
- __builtin_start_lifetime_as(p, false);
+ // bypasses the implicit-lifetime check
+ __builtin_start_lifetime_as(p, false);
- // Flag must be an ICE (Integer Constant Expression)
- __builtin_start_lifetime_as(p, runtime_flag); // expected-error {{expression is not an integer constant expression}}
- }
+ // Flag must be an ICE (Integer Constant Expression)
+ __builtin_start_lifetime_as(p, runtime_flag); // expected-error {{expression is not an integer constant expression}}
+ }
} // namespace StrictnessFlag
namespace Templates {
@@ -105,4 +105,4 @@ namespace ConstexprEvaluation {
constexpr bool b = test_eval(); // expected-error {{constexpr variable 'b' must be initialized by a constant expression}} \
// expected-note {{in call to 'test_eval()'}}
-}
+} // namespace ConstexprEvaluation
More information about the cfe-commits
mailing list