Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,15 @@ Potentially Breaking Changes
options-related code has been moved out of the Driver into a separate library.
- The ``clangFrontend`` library no longer depends on ``clangDriver``, which may
break downstream projects that relied on this transitive dependency.
- Clang is now more precise with regards to the lifetime of temporary objects
such as when aggregates are passed by value to a function, resulting in
better sharing of stack slots and reduced stack usage. This change can lead
to use-after-scope related issues in code that unintentionally relied on the
previous behavior. If recompiling with ``-fsanitize=address`` shows a
use-after-scope warning, then this is likely the case, and the report printed
should be able to help users pinpoint where the use-after-scope is occurring.
Users can use ``-Xclang -sloppy-temporary-lifetimes`` to retain the old
behavior until they are able to find and resolve issues in their code.

C/C++ Language Potentially Breaking Changes
-------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/CodeGenOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,10 @@ ENUM_CODEGENOPT(ZeroCallUsedRegs, ZeroCallUsedRegsKind,
/// non-deleting destructors. (No effect on Microsoft ABI.)
CODEGENOPT(CtorDtorReturnThis, 1, 0, Benign)

/// Set via -Xclang -sloppy-temporary-lifetimes to disable emission of lifetime
/// marker intrinsic calls.
CODEGENOPT(NoLifetimeMarkersForTemporaries, 1, 0, Benign)

/// Enables emitting Import Call sections on supported targets that can be used
/// by the Windows kernel to enable import call optimization.
CODEGENOPT(ImportCallOptimization, 1, 0, Benign)
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Options/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -8151,6 +8151,11 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">,
def replaceable_function: Joined<["-"], "loader-replaceable-function=">,
MarshallingInfoStringVector<CodeGenOpts<"LoaderReplaceableFunctionNames">>;

def sloppy_temporary_lifetimes
: Flag<["-"], "sloppy-temporary-lifetimes">,
HelpText<"Don't emit lifetime markers for temporary objects">,
MarshallingInfoFlag<CodeGenOpts<"NoLifetimeMarkersForTemporaries">>;

} // let Visibility = [CC1Option]

//===----------------------------------------------------------------------===//
Expand Down
22 changes: 21 additions & 1 deletion clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4960,7 +4960,27 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
return;
}

args.add(EmitAnyExprToTemp(E), type);
AggValueSlot ArgSlot = AggValueSlot::ignored();
// For arguments with aggregate type, create an alloca to store
// the value. If the argument's type has a destructor, that destructor
// will run at the end of the full-expression; emit matching lifetime
// markers.
//
// FIXME: For types which don't have a destructor, consider using a
// narrower lifetime bound.
if (hasAggregateEvaluationKind(E->getType())) {
RawAddress ArgSlotAlloca = Address::invalid();
ArgSlot = CreateAggTemp(E->getType(), "agg.tmp", &ArgSlotAlloca);

// Emit a lifetime start/end for this temporary at the end of the full
// expression.
if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries &&
EmitLifetimeStart(ArgSlotAlloca.getPointer()))
pushFullExprCleanup<CallLifetimeEnd>(NormalEHLifetimeMarker,
ArgSlotAlloca);
}

args.add(EmitAnyExpr(E, ArgSlot), type);
}

QualType CodeGenFunction::getVarArgType(const Expr *Arg) {
Expand Down
98 changes: 98 additions & 0 deletions clang/test/CodeGen/lifetime-call-temp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \
// RUN: -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime
// RUN: %clang -cc1 -xc++ -std=c++17 -triple x86_64-apple-macos -O1 \
// RUN: -disable-llvm-passes %s -emit-llvm -o - -Wno-return-type-c-linkage | \
// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,CXX
// RUN: %clang -cc1 -xobjective-c -triple x86_64-apple-macos -O1 \
// RUN: -disable-llvm-passes %s -emit-llvm -o - | \
// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,OBJC
// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \
// RUN: -emit-llvm -o - -sloppy-temporary-lifetimes | \
// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=SLOPPY

typedef struct { int x[100]; } aggregate;

#ifdef __cplusplus
extern "C" {
#endif

void takes_aggregate(aggregate);
aggregate gives_aggregate();

// CHECK-LABEL: define void @t1
void t1() {
takes_aggregate(gives_aggregate());

// CHECK: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8
// CHECK: call void @llvm.lifetime.start.p0(ptr [[AGGTMP]])
// CHECK: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
// CHECK: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]])
// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGGTMP]])

// SLOPPY: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8
// SLOPPY-NEXT: call void (ptr, ...) @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
// SLOPPY-NEXT: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]])
}

// CHECK: declare {{.*}}llvm.lifetime.start
// CHECK: declare {{.*}}llvm.lifetime.end

#ifdef __cplusplus
// CXX: define void @t2
void t2() {
struct S {
S(aggregate) {}
};
S{gives_aggregate()};

// CXX: [[AGG:%.*]] = alloca %struct.aggregate
// CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]]
// CXX: call void @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGG]])
// CXX: call void @_ZZ2t2EN1SC1E9aggregate(ptr {{.*}}, ptr {{.*}} byval(%struct.aggregate) align 8 [[AGG]])
// CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]]
}

struct Dtor {
~Dtor();
};

void takes_dtor(Dtor);
Dtor gives_dtor();

// CXX: define void @t3
void t3() {
takes_dtor(gives_dtor());

// CXX: [[AGG:%.*]] = alloca %struct.Dtor
// CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]])
// CXX: call void @gives_dtor(ptr{{.*}}sret(%struct.Dtor) align 1 [[AGG]])
// CXX: call void @takes_dtor(ptr noundef [[AGG]])
// CXX: call void @_ZN4DtorD1Ev(ptr {{.*}} [[AGG]])
// CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]])
// CXX: ret void
}

#endif

#ifdef __OBJC__

@interface X
-m:(aggregate)x;
@end

// OBJC: define void @t4
void t4(X *x) {
[x m: gives_aggregate()];

// OBJC: [[AGG:%.*]] = alloca %struct.aggregate
// OBJC: call void @llvm.lifetime.start.p0(ptr [[AGG]]
// OBJC: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
// OBJC: call {{.*}}@objc_msgSend
// OBJC: call void @llvm.lifetime.end.p0(ptr [[AGG]]
}

#endif

#ifdef __cplusplus
}
#endif
50 changes: 50 additions & 0 deletions clang/test/CodeGen/lifetime-invoke-c.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -disable-llvm-passes -fexceptions | FileCheck %s

struct Trivial {
int x[100];
};

void cleanup(int *p) {}
void func(struct Trivial t);
struct Trivial gen(void);

// CHECK-LABEL: define dso_local void @test()
void test() {
int x __attribute__((cleanup(cleanup)));

// CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
// CHECK: %[[AGG2:.*]] = alloca %struct.Trivial

// CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG1]])
// CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG1]])

// CHECK: invoke void @func(ptr{{.*}} %[[AGG1]])
// CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]]

// CHECK: [[CONT1]]:
// CHECK-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])

// CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG2]])
// CHECK: invoke void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG2]])
// CHECK: invoke void @func(ptr{{.*}} %[[AGG2]])
// CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]]

// CHECK: [[CONT2]]:
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])

// CHECK: [[LPAD1]]:
// CHECK: landingpad
// CHECK: br label %[[EHCLEANUP:.*]]

// CHECK: [[LPAD2]]:
// CHECK: landingpad
// CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
// CHECK: br label %[[EHCLEANUP]]

// CHECK: [[EHCLEANUP]]:
// CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
// CHECK: call void @cleanup
func(gen());
func(gen());
}
47 changes: 47 additions & 0 deletions clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s

extern "C" {

struct Trivial {
int x[100];
};

void func_that_throws(Trivial t);

// CHECK-LABEL: define{{.*}} void @test()
void test() {
// CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
// CHECK: %[[AGG2:.*]] = alloca %struct.Trivial

// CHECK: call void @llvm.lifetime.start.p0(ptr{{.*}} %[[AGG1]])
// CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG1]])
// CHECK-NEXT: to label %[[CONT1:.*]] unwind label %[[LPAD1:.*]]

// CHECK: [[CONT1]]:
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr{{.*}} %[[AGG2]])
// CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG2]])
// CHECK-NEXT: to label %[[CONT2:.*]] unwind label %[[LPAD2:.*]]

// CHECK: [[CONT2]]:
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG2]])
// CHECK-DAG: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG1]])
// CHECK: br label %[[TRY_CONT:.*]]

// CHECK: [[LPAD1]]:
// CHECK: landingpad
// CHECK: br label %[[EHCLEANUP:.*]]

// CHECK: [[LPAD2]]:
// CHECK: landingpad
// CHECK: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG2]])
// CHECK: br label %[[EHCLEANUP]]

// CHECK: [[EHCLEANUP]]:
// CHECK: call void @llvm.lifetime.end.p0(ptr{{.*}} %[[AGG1]])
try {
func_that_throws(Trivial{0});
func_that_throws(Trivial{0});
} catch (...) {
}
}
} // end extern "C"
19 changes: 19 additions & 0 deletions clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -emit-llvm -O3 -disable-llvm-passes -o - %s | FileCheck %s

struct A {
float x, y, z, w;
};

void foo(A a);

// CHECK-LABEL: @_Z4testv
// CHECK: [[A:%.*]] = alloca [[STRUCT_A:%.*]], align 4, addrspace(5)
// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_A]], align 4, addrspace(5)
// CHECK-NEXT: [[A_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[A]] to ptr
// CHECK-NEXT: [[AGG_TMP_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[AGG_TMP]] to ptr
// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[A]]) #[[ATTR4:[0-9]+]]
// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[AGG_TMP]]) #[[ATTR4]]
void test() {
A a;
foo(a);
}
4 changes: 4 additions & 0 deletions clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const char * f(S s)
// CHECK: [[T2:%.*]] = alloca %class.T, align 4
// CHECK: [[T3:%.*]] = alloca %class.T, align 4
//
// CHECK: [[AGG:%.*]] = alloca %class.S, align 4
//
// FIXME: We could defer starting the lifetime of the return object of concat
// until the call.
// CHECK: call void @llvm.lifetime.start.p0(ptr [[T1]])
Expand All @@ -34,10 +36,12 @@ const char * f(S s)
// CHECK: [[T4:%.*]] = call noundef ptr @_ZN1TC1EPKc(ptr {{[^,]*}} [[T2]], ptr noundef @.str)
//
// CHECK: call void @llvm.lifetime.start.p0(ptr [[T3]])
// CHECK: call void @llvm.lifetime.start.p0(ptr [[AGG]])
// CHECK: [[T5:%.*]] = call noundef ptr @_ZN1TC1E1S(ptr {{[^,]*}} [[T3]], [2 x i32] %{{.*}})
//
// CHECK: call void @_ZNK1T6concatERKS_(ptr dead_on_unwind writable sret(%class.T) align 4 [[T1]], ptr {{[^,]*}} [[T2]], ptr noundef nonnull align 4 dereferenceable(16) [[T3]])
// CHECK: [[T6:%.*]] = call noundef ptr @_ZNK1T3strEv(ptr {{[^,]*}} [[T1]])
// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGG]])
//
// CHECK: call void @llvm.lifetime.end.p0(
// CHECK: call void @llvm.lifetime.end.p0(
Expand Down
4 changes: 4 additions & 0 deletions clang/test/CodeGenCoroutines/pr59181.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void foo() {
}

// CHECK: cleanup.cont:{{.*}}
// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG:%agg.tmp]])
// CHECK-NEXT: load i8
// CHECK-NEXT: trunc
// CHECK-NEXT: store i1 false
Expand All @@ -57,3 +58,6 @@ void foo() {
// CHECK-NOT: call void @llvm.lifetime
// CHECK: call void @llvm.coro.await.suspend.void(
// CHECK-NEXT: %{{[0-9]+}} = call i8 @llvm.coro.suspend(

// CHECK-LABEL: cond.end:
// check call @llvm.lifetime.end.p0(ptr [[AGG]])
Loading