[C11] Implement WG14 N1285 (temporary lifetimes) (#133472)

This feature largely models the same behavior as in C++11. It is
technically a breaking change between C99 and C11, so the paper is not
being backported to older language modes.

One difference between C++ and C is that things which are rvalues in C
are often lvalues in C++ (such as the result of a ternary operator or a
comma operator).

Fixes #96486
This commit is contained in:
Aaron Ballman 2025-04-10 08:12:14 -04:00 committed by GitHub
parent 71f629fc2a
commit 5c8ba28c75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 388 additions and 67 deletions

View File

@ -162,6 +162,18 @@ C23 Feature Support
- Fixed a bug where you could not cast a null pointer constant to type
``nullptr_t``. Fixes #GH133644.
C11 Feature Support
^^^^^^^^^^^^^^^^^^^
- Implemented `WG14 N1285 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1285.htm>`_
which introduces the notion of objects with a temporary lifetime. When an
expression resulting in an rvalue with structure or union type and that type
contains a member of array type, the expression result is an automatic storage
duration object with temporary lifetime which begins when the expression is
evaluated and ends at the evaluation of the containing full expression. This
functionality is also implemented for earlier C language modes because the
C99 semantics will never be implemented (it would require dynamic allocations
of memory which leaks, which users would not appreciate).
Non-comprehensive list of changes in this release
-------------------------------------------------

View File

@ -2265,11 +2265,18 @@ void CodeGenFunction::pushDestroy(QualType::DestructionKind dtorKind,
cleanupKind & EHCleanup);
}
void CodeGenFunction::pushLifetimeExtendedDestroy(
QualType::DestructionKind dtorKind, Address addr, QualType type) {
CleanupKind cleanupKind = getCleanupKind(dtorKind);
pushLifetimeExtendedDestroy(cleanupKind, addr, type, getDestroyer(dtorKind),
cleanupKind & EHCleanup);
}
void CodeGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
QualType type, Destroyer *destroyer,
bool useEHCleanupForArray) {
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type,
destroyer, useEHCleanupForArray);
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type, destroyer,
useEHCleanupForArray);
}
// Pushes a destroy and defers its deactivation until its

View File

@ -393,55 +393,49 @@ pushTemporaryCleanup(CodeGenFunction &CGF, const MaterializeTemporaryExpr *M,
}
}
CXXDestructorDecl *ReferenceTemporaryDtor = nullptr;
if (const RecordType *RT =
E->getType()->getBaseElementTypeUnsafe()->getAs<RecordType>()) {
// Get the destructor for the reference temporary.
auto *ClassDecl = cast<CXXRecordDecl>(RT->getDecl());
if (!ClassDecl->hasTrivialDestructor())
ReferenceTemporaryDtor = ClassDecl->getDestructor();
}
QualType::DestructionKind DK = E->getType().isDestructedType();
if (DK != QualType::DK_none) {
switch (M->getStorageDuration()) {
case SD_Static:
case SD_Thread: {
CXXDestructorDecl *ReferenceTemporaryDtor = nullptr;
if (const RecordType *RT =
E->getType()->getBaseElementTypeUnsafe()->getAs<RecordType>()) {
// Get the destructor for the reference temporary.
if (auto *ClassDecl = dyn_cast<CXXRecordDecl>(RT->getDecl());
ClassDecl && !ClassDecl->hasTrivialDestructor())
ReferenceTemporaryDtor = ClassDecl->getDestructor();
}
if (!ReferenceTemporaryDtor)
return;
if (!ReferenceTemporaryDtor)
return;
// Call the destructor for the temporary.
switch (M->getStorageDuration()) {
case SD_Static:
case SD_Thread: {
llvm::FunctionCallee CleanupFn;
llvm::Constant *CleanupArg;
if (E->getType()->isArrayType()) {
CleanupFn = CodeGenFunction(CGF.CGM).generateDestroyHelper(
ReferenceTemporary, E->getType(),
CodeGenFunction::destroyCXXObject, CGF.getLangOpts().Exceptions,
dyn_cast_or_null<VarDecl>(M->getExtendingDecl()));
CleanupArg = llvm::Constant::getNullValue(CGF.Int8PtrTy);
} else {
CleanupFn = CGF.CGM.getAddrAndTypeOfCXXStructor(
GlobalDecl(ReferenceTemporaryDtor, Dtor_Complete));
CleanupArg = cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(CGF));
llvm::FunctionCallee CleanupFn;
llvm::Constant *CleanupArg;
if (E->getType()->isArrayType()) {
CleanupFn = CodeGenFunction(CGF.CGM).generateDestroyHelper(
ReferenceTemporary, E->getType(), CodeGenFunction::destroyCXXObject,
CGF.getLangOpts().Exceptions,
dyn_cast_or_null<VarDecl>(M->getExtendingDecl()));
CleanupArg = llvm::Constant::getNullValue(CGF.Int8PtrTy);
} else {
CleanupFn = CGF.CGM.getAddrAndTypeOfCXXStructor(
GlobalDecl(ReferenceTemporaryDtor, Dtor_Complete));
CleanupArg =
cast<llvm::Constant>(ReferenceTemporary.emitRawPointer(CGF));
}
CGF.CGM.getCXXABI().registerGlobalDtor(
CGF, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
} break;
case SD_FullExpression:
CGF.pushDestroy(DK, ReferenceTemporary, E->getType());
break;
case SD_Automatic:
CGF.pushLifetimeExtendedDestroy(DK, ReferenceTemporary, E->getType());
break;
case SD_Dynamic:
llvm_unreachable("temporary cannot have dynamic storage duration");
}
CGF.CGM.getCXXABI().registerGlobalDtor(
CGF, *cast<VarDecl>(M->getExtendingDecl()), CleanupFn, CleanupArg);
break;
}
case SD_FullExpression:
CGF.pushDestroy(NormalAndEHCleanup, ReferenceTemporary, E->getType(),
CodeGenFunction::destroyCXXObject,
CGF.getLangOpts().Exceptions);
break;
case SD_Automatic:
CGF.pushLifetimeExtendedDestroy(NormalAndEHCleanup,
ReferenceTemporary, E->getType(),
CodeGenFunction::destroyCXXObject,
CGF.getLangOpts().Exceptions);
break;
case SD_Dynamic:
llvm_unreachable("temporary cannot have dynamic storage duration");
}
}

View File

@ -2249,6 +2249,8 @@ public:
void pushLifetimeExtendedDestroy(CleanupKind kind, Address addr,
QualType type, Destroyer *destroyer,
bool useEHCleanupForArray);
void pushLifetimeExtendedDestroy(QualType::DestructionKind dtorKind,
Address addr, QualType type);
void pushCallObjectDeleteCleanup(const FunctionDecl *OperatorDelete,
llvm::Value *CompletePtr,
QualType ElementType);

View File

@ -7650,11 +7650,14 @@ Sema::CreateMaterializeTemporaryExpr(QualType T, Expr *Temporary,
}
ExprResult Sema::TemporaryMaterializationConversion(Expr *E) {
// In C++98, we don't want to implicitly create an xvalue.
// In C++98, we don't want to implicitly create an xvalue. C11 added the
// same rule, but C99 is broken without this behavior and so we treat the
// change as applying to all C language modes.
// FIXME: This means that AST consumers need to deal with "prvalues" that
// denote materialized temporaries. Maybe we should add another ValueKind
// for "xvalue pretending to be a prvalue" for C++98 support.
if (!E->isPRValue() || !getLangOpts().CPlusPlus11)
if (!E->isPRValue() ||
(!getLangOpts().CPlusPlus11 && getLangOpts().CPlusPlus))
return E;
// C++1z [conv.rval]/1: T shall be a complete type.

View File

@ -1,24 +1,82 @@
// RUN: %clang_cc1 -verify=wrong -std=c99 %s
// RUN: %clang_cc1 -verify=wrong -std=c11 %s
// RUN: %clang_cc1 -verify=cpp -std=c++11 -x c++ %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c -std=c99 %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected,c -std=c11 %s
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cpp -std=c++11 -x c++ %s
/* WG14 N1285: No
/* WG14 N1285: Clang 21
* Extending the lifetime of temporary objects (factored approach)
*
* NB: we do not properly materialize temporary expressions in situations where
* it would be expected; that is why the "no-diagnostics" marking is named
* "wrong". We do issue the expected diagnostic in C++ mode.
* This paper introduced the notion of an object with a temporary lifetime. Any
* operation resulting in an rvalue of structure or union type which contains
* an array results in an object with temporary lifetime.
*
* Even though this is a change for C11, we treat it as a DR and apply it
* retroactively to earlier C language modes.
*/
// wrong-no-diagnostics
// C11 6.2.4p8: A non-lvalue expression with structure or union type, where the
// structure or union contains a member with array type (including,
// recursively, members of all contained structures and unions) refers to an
// object with automatic storage duration and temporary lifetime. Its lifetime
// begins when the expression is evaluated and its initial value is the value
// of the expression. Its lifetime ends when the evaluation of the containing
// full expression or full declarator ends. Any attempt to modify an object
// with temporary lifetime results in undefined behavior.
struct X { int a[5]; };
struct X f(void);
int foo(void) {
// FIXME: This diagnostic should be issued in C11 as well (though not in C99,
// as this paper was a breaking change between C99 and C11).
int *p = f().a; // cpp-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
return *p;
union U { int a[10]; double d; };
union U g(void);
void sink(int *);
int func_return(void) {
int *p = f().a; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = f().a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
p = g().a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink(f().a); // Ok
return *f().a; // Ok
}
int ternary(void) {
int *p = (1 ? (struct X){ 0 } : f()).a; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
int *r = (1 ? (union U){ 0 } : g()).a; // expected-warning {{temporary whose address is used as value of local variable 'r' will be destroyed at the end of the full-expression}}
p = (1 ? (struct X){ 0 } : f()).a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink((1 ? (struct X){ 0 } : f()).a); // Ok
// This intentionally gets one diagnostic in C and two in C++. In C, the
// compound literal results in an lvalue, not an rvalue as it does in C++. So
// only one branch results in a temporary in C but both branches do in C++.
int *q = 1 ? (struct X){ 0 }.a : f().a; // expected-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}} \
cpp-warning {{temporary whose address is used as value of local variable 'q' will be destroyed at the end of the full-expression}}
q = 1 ? (struct X){ 0 }.a : f().a; // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}} \
cpp-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
q = 1 ? (struct X){ 0 }.a : g().a; // expected-warning {{object backing the pointer q will be destroyed at the end of the full-expression}} \
cpp-warning {{object backing the pointer q will be destroyed at the end of the full-expression}}
sink(1 ? (struct X){ 0 }.a : f().a); // Ok
return *(1 ? (struct X){ 0 }.a : f().a); // Ok
}
int comma(void) {
struct X x;
int *p = ((void)0, x).a; // c-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = ((void)0, x).a; // c-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink(((void)0, x).a); // Ok
return *(((void)0, x).a);// Ok
}
int cast(void) {
struct X x;
int *p = ((struct X)x).a; // expected-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = ((struct X)x).a; // expected-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink(((struct X)x).a); // Ok
return *(((struct X)x).a); // Ok
}
int assign(void) {
struct X x, s;
int *p = (x = s).a; // c-warning {{temporary whose address is used as value of local variable 'p' will be destroyed at the end of the full-expression}}
p = (x = s).a; // c-warning {{object backing the pointer p will be destroyed at the end of the full-expression}}
sink((x = s).a); // Ok
return *((x = s).a); // Ok
}

244
clang/test/C/C11/n1285_1.c Normal file
View File

@ -0,0 +1,244 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
// RUN: %clang_cc1 -std=c99 -Wno-dangling -emit-llvm -o - %s | FileCheck %s --check-prefix=CHECK
// RUN: %clang_cc1 -std=c11 -Wno-dangling -emit-llvm -o - %s | FileCheck %s --check-prefix=CHECK
// RUN: %clang_cc1 -std=c11 -O2 -disable-llvm-passes -Wno-dangling -emit-llvm -o - %s | FileCheck %s --check-prefix=C11-O2
// Ensure that codegen for temporary lifetimes makes sense.
struct X { int a[5]; };
struct X f(void);
// CHECK-LABEL: define dso_local i32 @func_return(
// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[P:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// CHECK-NEXT: call void @f(ptr dead_on_unwind writable sret([[STRUCT_X]]) align 4 [[REF_TMP]])
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr [[P]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P]], align 8
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
// CHECK-NEXT: ret i32 [[TMP1]]
//
// C11-O2-LABEL: define dso_local i32 @func_return(
// C11-O2-SAME: ) #[[ATTR0:[0-9]+]] {
// C11-O2-NEXT: [[ENTRY:.*:]]
// C11-O2-NEXT: [[P:%.*]] = alloca ptr, align 8
// C11-O2-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 8, ptr [[P]]) #[[ATTR5:[0-9]+]]
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: call void @f(ptr dead_on_unwind writable sret([[STRUCT_X]]) align 4 [[REF_TMP]])
// C11-O2-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// C11-O2-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: store ptr [[ARRAYDECAY]], ptr [[P]], align 8, !tbaa [[TBAA3:![0-9]+]]
// C11-O2-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P]], align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4, !tbaa [[TBAA8:![0-9]+]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 8, ptr [[P]]) #[[ATTR5]]
// C11-O2-NEXT: ret i32 [[TMP1]]
//
int func_return(void) {
int *p = f().a;
return *p;
}
// CHECK-LABEL: define dso_local i32 @ternary(
// CHECK-SAME: ) #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// CHECK-NEXT: [[Q:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[DOTCOMPOUNDLITERAL:%.*]] = alloca [[STRUCT_X]], align 4
// CHECK-NEXT: br i1 true, label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
// CHECK: [[COND_TRUE]]:
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[REF_TMP]], i8 0, i64 20, i1 false)
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// CHECK-NEXT: br label %[[COND_END:.*]]
// CHECK: [[COND_FALSE]]:
// CHECK-NEXT: call void @f(ptr dead_on_unwind writable sret([[STRUCT_X]]) align 4 [[REF_TMP]])
// CHECK-NEXT: br label %[[COND_END]]
// CHECK: [[COND_END]]:
// CHECK-NEXT: [[A1:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A1]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[DOTCOMPOUNDLITERAL]], i8 0, i64 20, i1 false)
// CHECK-NEXT: [[A2:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[DOTCOMPOUNDLITERAL]], i32 0, i32 0
// CHECK-NEXT: [[A3:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[DOTCOMPOUNDLITERAL]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY4:%.*]] = getelementptr inbounds [5 x i32], ptr [[A3]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY4]], ptr [[Q]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
// CHECK-NEXT: [[TMP2:%.*]] = load ptr, ptr [[Q]], align 8
// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[TMP2]], align 4
// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[TMP3]]
// CHECK-NEXT: ret i32 [[ADD]]
//
// C11-O2-LABEL: define dso_local i32 @ternary(
// C11-O2-SAME: ) #[[ATTR0]] {
// C11-O2-NEXT: [[ENTRY:.*:]]
// C11-O2-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// C11-O2-NEXT: [[Q:%.*]] = alloca ptr, align 8
// C11-O2-NEXT: [[DOTCOMPOUNDLITERAL:%.*]] = alloca [[STRUCT_X]], align 4
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: br i1 true, label %[[COND_TRUE:.*]], label %[[COND_FALSE:.*]]
// C11-O2: [[COND_TRUE]]:
// C11-O2-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[REF_TMP]], i8 0, i64 20, i1 false)
// C11-O2-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// C11-O2-NEXT: br label %[[COND_END:.*]]
// C11-O2: [[COND_FALSE]]:
// C11-O2-NEXT: call void @f(ptr dead_on_unwind writable sret([[STRUCT_X]]) align 4 [[REF_TMP]])
// C11-O2-NEXT: br label %[[COND_END]]
// C11-O2: [[COND_END]]:
// C11-O2-NEXT: [[A1:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// C11-O2-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A1]], i64 0, i64 0
// C11-O2-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 8, ptr [[Q]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[DOTCOMPOUNDLITERAL]], i8 0, i64 20, i1 false)
// C11-O2-NEXT: [[A2:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[DOTCOMPOUNDLITERAL]], i32 0, i32 0
// C11-O2-NEXT: [[A3:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[DOTCOMPOUNDLITERAL]], i32 0, i32 0
// C11-O2-NEXT: [[ARRAYDECAY4:%.*]] = getelementptr inbounds [5 x i32], ptr [[A3]], i64 0, i64 0
// C11-O2-NEXT: store ptr [[ARRAYDECAY4]], ptr [[Q]], align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4, !tbaa [[TBAA8]]
// C11-O2-NEXT: [[TMP2:%.*]] = load ptr, ptr [[Q]], align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: [[TMP3:%.*]] = load i32, ptr [[TMP2]], align 4, !tbaa [[TBAA8]]
// C11-O2-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[TMP3]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 8, ptr [[Q]]) #[[ATTR5]]
// C11-O2-NEXT: ret i32 [[ADD]]
//
int ternary(void) {
extern int *p;
p = (1 ? (struct X){ 0 } : f()).a;
int *q = 1 ? (struct X){ 0 }.a : f().a;
return *p + *q;
}
// CHECK-LABEL: define dso_local i32 @comma(
// CHECK-SAME: ) #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[X:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X]], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[REF_TMP]], ptr align 4 [[X]], i64 20, i1 false)
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
// CHECK-NEXT: ret i32 [[TMP1]]
//
// C11-O2-LABEL: define dso_local i32 @comma(
// C11-O2-SAME: ) #[[ATTR0]] {
// C11-O2-NEXT: [[ENTRY:.*:]]
// C11-O2-NEXT: [[X:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// C11-O2-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X]], align 4
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[X]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[REF_TMP]], ptr align 4 [[X]], i64 20, i1 false), !tbaa.struct [[TBAA_STRUCT10:![0-9]+]]
// C11-O2-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// C11-O2-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// C11-O2-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4, !tbaa [[TBAA8]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[X]]) #[[ATTR5]]
// C11-O2-NEXT: ret i32 [[TMP1]]
//
int comma(void) {
struct X x;
extern int *p;
p = ((void)0, x).a;
return *p;
}
// CHECK-LABEL: define dso_local i32 @cast(
// CHECK-SAME: ) #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[X:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X]], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[REF_TMP]], ptr align 4 [[X]], i64 20, i1 false)
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
// CHECK-NEXT: ret i32 [[TMP1]]
//
// C11-O2-LABEL: define dso_local i32 @cast(
// C11-O2-SAME: ) #[[ATTR0]] {
// C11-O2-NEXT: [[ENTRY:.*:]]
// C11-O2-NEXT: [[X:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// C11-O2-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X]], align 4
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[X]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[REF_TMP]], ptr align 4 [[X]], i64 20, i1 false), !tbaa.struct [[TBAA_STRUCT10]]
// C11-O2-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// C11-O2-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// C11-O2-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4, !tbaa [[TBAA8]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[X]]) #[[ATTR5]]
// C11-O2-NEXT: ret i32 [[TMP1]]
//
int cast(void) {
struct X x;
extern int *p;
p = ((struct X)x).a;
return *p;
}
// CHECK-LABEL: define dso_local i32 @assign(
// CHECK-SAME: ) #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[X:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// CHECK-NEXT: [[S:%.*]] = alloca [[STRUCT_X]], align 4
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X]], align 4
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[X]], ptr align 4 [[S]], i64 20, i1 false)
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[REF_TMP]], ptr align 4 [[X]], i64 20, i1 false)
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
// CHECK-NEXT: ret i32 [[TMP1]]
//
// C11-O2-LABEL: define dso_local i32 @assign(
// C11-O2-SAME: ) #[[ATTR0]] {
// C11-O2-NEXT: [[ENTRY:.*:]]
// C11-O2-NEXT: [[X:%.*]] = alloca [[STRUCT_X:%.*]], align 4
// C11-O2-NEXT: [[S:%.*]] = alloca [[STRUCT_X]], align 4
// C11-O2-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_X]], align 4
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[X]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[S]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.lifetime.start.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[X]], ptr align 4 [[S]], i64 20, i1 false), !tbaa.struct [[TBAA_STRUCT10]]
// C11-O2-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 [[REF_TMP]], ptr align 4 [[X]], i64 20, i1 false), !tbaa.struct [[TBAA_STRUCT10]]
// C11-O2-NEXT: [[A:%.*]] = getelementptr inbounds nuw [[STRUCT_X]], ptr [[REF_TMP]], i32 0, i32 0
// C11-O2-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [5 x i32], ptr [[A]], i64 0, i64 0
// C11-O2-NEXT: store ptr [[ARRAYDECAY]], ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[REF_TMP]]) #[[ATTR5]]
// C11-O2-NEXT: [[TMP0:%.*]] = load ptr, ptr @p, align 8, !tbaa [[TBAA3]]
// C11-O2-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4, !tbaa [[TBAA8]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[S]]) #[[ATTR5]]
// C11-O2-NEXT: call void @llvm.lifetime.end.p0(i64 20, ptr [[X]]) #[[ATTR5]]
// C11-O2-NEXT: ret i32 [[TMP1]]
//
int assign(void) {
struct X x, s;
extern int *p;
p = (x = s).a;
return *p;
}
//.
// C11-O2: [[TBAA3]] = !{[[META4:![0-9]+]], [[META4]], i64 0}
// C11-O2: [[META4]] = !{!"p1 int", [[META5:![0-9]+]], i64 0}
// C11-O2: [[META5]] = !{!"any pointer", [[META6:![0-9]+]], i64 0}
// C11-O2: [[META6]] = !{!"omnipotent char", [[META7:![0-9]+]], i64 0}
// C11-O2: [[META7]] = !{!"Simple C/C++ TBAA"}
// C11-O2: [[TBAA8]] = !{[[META9:![0-9]+]], [[META9]], i64 0}
// C11-O2: [[META9]] = !{!"int", [[META6]], i64 0}
// C11-O2: [[TBAA_STRUCT10]] = !{i64 0, i64 20, [[META11:![0-9]+]]}
// C11-O2: [[META11]] = !{[[META6]], [[META6]], i64 0}
//.

View File

@ -27,7 +27,7 @@ void testva (int n, ...) {
va_start(ap,n);
// CHECK: [[AP:%[a-z0-9]+]] = alloca ptr, align 4
// CHECK: [[V5:%[a-z0-9]+]] = alloca %struct.x, align 4
// CHECK: [[TMP:%[a-z0-9]+]] = alloca [4 x i32], align 4
// CHECK: [[TMP:%[a-z0-9.]+]] = alloca [4 x i32], align 4
// CHECK: call void @llvm.va_start.p0(ptr [[AP]])
char* v1 = va_arg (ap, char*);

View File

@ -23,7 +23,8 @@ typedef struct _GLKMatrix4 GLKMatrix4;
}
@end
// CHECK: [[M:%.*]] = getelementptr inbounds nuw %struct._GLKMatrix4, ptr [[TMP:%.*]], i32 0, i32 0
// CHECK: [[REF_TEMP:%.*]] = alloca %struct._GLKMatrix4, align 4
// CHECK: [[M:%.*]] = getelementptr inbounds nuw %struct._GLKMatrix4, ptr [[REF_TEMP:%.*]], i32 0, i32 0
// CHECK: [[ARRAYDECAY:%.*]] = getelementptr inbounds [16 x float], ptr [[M]], i64 0, i64 0
// CHECK: [[SIX:%.*]] = load ptr, ptr @OBJC_SELECTOR_REFERENCES
// CHECK: call void @objc_msgSend(ptr noundef [[SEVEN:%.*]], ptr noundef [[SIX]], ptr noundef [[ARRAYDECAY]])
// CHECK: call void @objc_msgSend(ptr noundef [[SEVEN:%.*]], ptr noundef [[SIX]], ptr noundef [[ARRAYDECAY]])

View File

@ -941,7 +941,7 @@ conformance.</p>
<tr>
<td>Extending the lifetime of temporary objects (factored approach)</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1285.htm">N1285</a></td>
<td class="none" align="center">No</td>
<td class="unreleased" align="center">Clang 21</td>
</tr>
<tr>
<td>Requiring signed char to have no padding bits</td>