// RUN: %clang_cc1 --std=c++20 -fexceptions -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck -check-prefixes=EH %s
// RUN: %clang_cc1 --std=c++20 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck -check-prefixes=NOEH,CHECK %s
struct Printy {
Printy(const char *name) : name(name) {}
~Printy() {}
const char *name;
};
int foo() { return 2; }
struct Printies {
Printy a;
Printy b;
Printy c;
};
void ParenInit() {
// CHECK-LABEL: define dso_local void @_Z9ParenInitv()
// CHECK: [[CLEANUP_DEST:%.+]] = alloca i32, align 4
Printies ps(Printy("a"),
// CHECK: call void @_ZN6PrintyC1EPKc
({
if (foo()) return;
// CHECK: if.then:
// CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
// CHECK-NEXT: br label %cleanup
Printy("b");
// CHECK: if.end:
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
}),
({
if (foo()) return;
// CHECK: if.then{{.*}}:
// CHECK-NEXT: store i32 1, ptr [[CLEANUP_DEST]], align 4
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: br label %cleanup
Printy("c");
// CHECK: if.end{{.*}}:
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: call void @_ZN8PrintiesD1Ev
// CHECK-NEXT: br label %return
}));
// CHECK: cleanup:
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: br label %return
}
void break_in_stmt_expr() {
// Verify that the "break" in "if.then".calls dtor before jumping to "for.end".
// CHECK-LABEL: define dso_local void @_Z18break_in_stmt_exprv()
Printies p{Printy("a"),
// CHECK: call void @_ZN6PrintyC1EPKc
({
for (;;) {
Printies ps{
Printy("b"),
// CHECK: for.cond:
// CHECK: call void @_ZN6PrintyC1EPKc
({
if (foo()) {
break;
// CHECK: if.then:
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: br label %for.end
}
Printy("c");
// CHECK: if.end:
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
}),
Printy("d")};
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: call void @_ZN8PrintiesD1Ev
// CHECK-NEXT: br label %for.cond
}
Printy("e");
// CHECK: for.end:
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
}),
Printy("f")};
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: call void @_ZN8PrintiesD1Ev
}
void goto_in_stmt_expr() {
// Verify that:
// - correct branch fixups for deactivated normal cleanups are generated correctly.
// CHECK-LABEL: define dso_local void @_Z17goto_in_stmt_exprv()
// CHECK: [[CLEANUP_DEST_SLOT:%cleanup.dest.slot.*]] = alloca i32, align 4
{
Printies p1{Printy("a"), // CHECK: call void @_ZN6PrintyC1EPKc
({
{
Printies p2{Printy("b"),
// CHECK: call void @_ZN6PrintyC1EPKc
({
if (foo() == 1) {
goto in;
// CHECK: if.then:
// CHECK-NEXT: store i32 2, ptr [[CLEANUP_DEST_SLOT]], align 4
// CHECK-NEXT: br label %[[CLEANUP1:.+]]
}
if (foo() == 2) {
goto out;
// CHECK: if.then{{.*}}:
// CHECK-NEXT: store i32 3, ptr [[CLEANUP_DEST_SLOT]], align 4
// CHECK-NEXT: br label %[[CLEANUP1]]
}
Printy("c");
// CHECK: if.end{{.*}}:
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
}),
Printy("d")};
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: call void @_ZN8PrintiesD1Ev
// CHECK-NEXT: br label %in
}
in:
Printy("e");
// CHECK: in: ; preds = %if.end{{.*}}, %[[CLEANUP1]]
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
}),
Printy("f")};
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: call void @_ZN8PrintiesD1Ev
// CHECK-NEXT: br label %out
}
out:
return;
// CHECK: out:
// CHECK-NEXT: ret void
// CHECK: [[CLEANUP1]]: ; preds = %if.then{{.*}}, %if.then
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: %cleanup.dest = load i32, ptr [[CLEANUP_DEST_SLOT]], align 4
// CHECK-NEXT: switch i32 %cleanup.dest, label %[[CLEANUP2:.+]] [
// CHECK-NEXT: i32 2, label %in
// CHECK-NEXT: ]
// CHECK: [[CLEANUP2]]: ; preds = %[[CLEANUP1]]
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: %cleanup.dest{{.*}} = load i32, ptr [[CLEANUP_DEST_SLOT]], align 4
// CHECK-NEXT: switch i32 %cleanup.dest{{.*}}, label %unreachable [
// CHECK-NEXT: i32 3, label %out
// CHECK-NEXT: ]
}
void ArrayInit() {
// Printy arr[4] = {ctorA, ctorB, stmt-exprC, stmt-exprD};
// Verify that:
// - We do the necessary stores for array cleanups (endOfInit and last constructed element).
// - We update the array init element correctly for ctorA, ctorB and stmt-exprC.
// - stmt-exprC and stmt-exprD share the array body dtor code (see %cleanup).
// CHECK-LABEL: define dso_local void @_Z9ArrayInitv()
// CHECK: %arrayinit.endOfInit = alloca ptr, align 8
// CHECK: %cleanup.dest.slot = alloca i32, align 4
// CHECK: store ptr %arr, ptr %arrayinit.endOfInit, align 8
Printy arr[4] = {
Printy("a"),
// CHECK: call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) %arr, ptr noundef @.str)
// CHECK: [[ARRAYINIT_ELEMENT1:%.+]] = getelementptr inbounds %struct.Printy, ptr %arr, i64 1
// CHECK: store ptr [[ARRAYINIT_ELEMENT1]], ptr %arrayinit.endOfInit, align 8
Printy("b"),
// CHECK: call void @_ZN6PrintyC1EPKc(ptr noundef nonnull align 8 dereferenceable(8) [[ARRAYINIT_ELEMENT1]], ptr noundef @.str.1)
// CHECK: [[ARRAYINIT_ELEMENT2:%.+]] = getelementptr inbounds %struct.Printy, ptr %arr, i64 2
// CHECK: store ptr [[ARRAYINIT_ELEMENT2]], ptr %arrayinit.endOfInit, align 8
({
// CHECK: br i1 {{.*}}, label %if.then, label %if.end
if (foo()) {
return;
// CHECK: if.then:
// CHECK-NEXT: store i32 1, ptr %cleanup.dest.slot, align 4
// CHECK-NEXT: br label %cleanup
}
// CHECK: if.end:
Printy("c");
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: %arrayinit.element2 = getelementptr inbounds %struct.Printy, ptr %arr, i64 3
// CHECK-NEXT: store ptr %arrayinit.element2, ptr %arrayinit.endOfInit, align 8
}),
({
// CHECK: br i1 {{%.+}} label %[[IF_THEN2:.+]], label %[[IF_END2:.+]]
if (foo()) {
return;
// CHECK: [[IF_THEN2]]:
// CHECK-NEXT: store i32 1, ptr %cleanup.dest.slot, align 4
// CHECK-NEXT: br label %cleanup
}
// CHECK: [[IF_END2]]:
Printy("d");
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: %array.begin = getelementptr inbounds [4 x %struct.Printy], ptr %arr, i32 0, i32 0
// CHECK-NEXT: %0 = getelementptr inbounds %struct.Printy, ptr %array.begin, i64 4
// CHECK-NEXT: br label %[[ARRAY_DESTROY_BODY1:.+]]
}),
};
// CHECK: [[ARRAY_DESTROY_BODY1]]:
// CHECK-NEXT: %arraydestroy.elementPast{{.*}} = phi ptr [ %0, %[[IF_END2]] ], [ %arraydestroy.element{{.*}}, %[[ARRAY_DESTROY_BODY1]] ]
// CHECK-NEXT: %arraydestroy.element{{.*}} = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast{{.*}}, i64 -1
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: %arraydestroy.done{{.*}} = icmp eq ptr %arraydestroy.element{{.*}}, %array.begin
// CHECK-NEXT: br i1 %arraydestroy.done{{.*}}, label %[[ARRAY_DESTROY_DONE1:.+]], label %[[ARRAY_DESTROY_BODY1]]
// CHECK: [[ARRAY_DESTROY_DONE1]]:
// CHECK-NEXT: ret void
// CHECK: cleanup:
// CHECK-NEXT: %1 = load ptr, ptr %arrayinit.endOfInit, align 8
// CHECK-NEXT: %arraydestroy.isempty = icmp eq ptr %arr, %1
// CHECK-NEXT: br i1 %arraydestroy.isempty, label %[[ARRAY_DESTROY_DONE2:.+]], label %[[ARRAY_DESTROY_BODY2:.+]]
// CHECK: [[ARRAY_DESTROY_BODY2]]:
// CHECK-NEXT: %arraydestroy.elementPast = phi ptr [ %1, %cleanup ], [ %arraydestroy.element, %[[ARRAY_DESTROY_BODY2]] ]
// CHECK-NEXT: %arraydestroy.element = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast, i64 -1
// CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element)
// CHECK-NEXT: %arraydestroy.done = icmp eq ptr %arraydestroy.element, %arr
// CHECK-NEXT: br i1 %arraydestroy.done, label %[[ARRAY_DESTROY_DONE2]], label %[[ARRAY_DESTROY_BODY2]]
// CHECK: [[ARRAY_DESTROY_DONE2]]:
// CHECK-NEXT: br label %[[ARRAY_DESTROY_DONE1]]
}
void ArraySubobjects() {
struct S {
Printy arr1[2];
Printy arr2[2];
Printy p;
};
// CHECK-LABEL: define dso_local void @_Z15ArraySubobjectsv()
// CHECK: %arrayinit.endOfInit = alloca ptr, align 8
S s{{Printy("a"), Printy("b")},
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK: call void @_ZN6PrintyC1EPKc
{Printy("a"),
// CHECK: store ptr %arr2, ptr %arrayinit.endOfInit, align 8
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK: [[ARRAYINIT_ELEMENT:%.+]] = getelementptr inbounds %struct.Printy
// CHECK: store ptr [[ARRAYINIT_ELEMENT]], ptr %arrayinit.endOfInit, align 8
({
if (foo()) {
return;
// CHECK: if.then:
// CHECK-NEXT: [[V0:%.+]] = load ptr, ptr %arrayinit.endOfInit, align 8
// CHECK-NEXT: %arraydestroy.isempty = icmp eq ptr %arr2, [[V0]]
// CHECK-NEXT: br i1 %arraydestroy.isempty, label %[[ARRAY_DESTROY_DONE:.+]], label %[[ARRAY_DESTROY_BODY:.+]]
}
Printy("b");
})
},
Printy("c")
// CHECK: if.end:
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: call void @_ZZ15ArraySubobjectsvEN1SD1Ev
// CHECK-NEXT: br label %return
};
// CHECK: return:
// CHECK-NEXT: ret void
// CHECK: [[ARRAY_DESTROY_BODY]]:
// CHECK-NEXT: %arraydestroy.elementPast = phi ptr [ %0, %if.then ], [ %arraydestroy.element, %[[ARRAY_DESTROY_BODY]] ]
// CHECK-NEXT: %arraydestroy.element = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast, i64 -1
// CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element)
// CHECK-NEXT: %arraydestroy.done = icmp eq ptr %arraydestroy.element, %arr2
// CHECK-NEXT: br i1 %arraydestroy.done, label %[[ARRAY_DESTROY_DONE]], label %[[ARRAY_DESTROY_BODY]]
// CHECK: [[ARRAY_DESTROY_DONE]]
// CHECK-NEXT: [[ARRAY_BEGIN:%.+]] = getelementptr inbounds [2 x %struct.Printy], ptr %arr1, i32 0, i32 0
// CHECK-NEXT: [[V1:%.+]] = getelementptr inbounds %struct.Printy, ptr [[ARRAY_BEGIN]], i64 2
// CHECK-NEXT: br label %[[ARRAY_DESTROY_BODY2:.+]]
// CHECK: [[ARRAY_DESTROY_BODY2]]:
// CHECK-NEXT: %arraydestroy.elementPast4 = phi ptr [ %1, %[[ARRAY_DESTROY_DONE]] ], [ %arraydestroy.element5, %[[ARRAY_DESTROY_BODY2]] ]
// CHECK-NEXT: %arraydestroy.element5 = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast4, i64 -1
// CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element5)
// CHECK-NEXT: %arraydestroy.done6 = icmp eq ptr %arraydestroy.element5, [[ARRAY_BEGIN]]
// CHECK-NEXT: br i1 %arraydestroy.done6, label %[[ARRAY_DESTROY_DONE2:.+]], label %[[ARRAY_DESTROY_BODY2]]
// CHECK: [[ARRAY_DESTROY_DONE2]]:
// CHECK-NEXT: br label %return
}
void LambdaInit() {
// CHECK-LABEL: define dso_local void @_Z10LambdaInitv()
auto S = [a = Printy("a"), b = ({
if (foo()) {
return;
// CHECK: if.then:
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: br label %return
}
Printy("b");
})]() { return a; };
}
struct PrintyRefBind {
const Printy &a;
const Printy &b;
};
struct Temp {
Temp();
~Temp();
};
Temp CreateTemp();
Printy CreatePrinty();
Printy CreatePrinty(const Temp&);
void LifetimeExtended() {
// CHECK-LABEL: define dso_local void @_Z16LifetimeExtendedv
PrintyRefBind ps = {Printy("a"), ({
if (foo()) {
return;
// CHECK: if.then:
// CHECK-NEXT: call void @_ZN6PrintyD1Ev
// CHECK-NEXT: br label %return
}
Printy("b");
})};
}
void ConditionalLifetimeExtended() {
// CHECK-LABEL: @_Z27ConditionalLifetimeExtendedv()
// Verify that we create two cleanup flags.
// 1. First for the cleanup which is deactivated after full expression.
// 2. Second for the life-ext cleanup which is activated if the branch is taken.
// Note: We use `CreateTemp()` to ensure that life-ext destroy cleanup is not at
// the top of EHStack on deactivation. This ensures using active flags.
Printy* p1 = nullptr;
// CHECK: store i1 false, ptr [[BRANCH1_DEFERRED:%cleanup.cond]], align 1
// CHECK-NEXT: store i1 false, ptr [[BRANCH1_LIFEEXT:%cleanup.cond.*]], align 1
PrintyRefBind ps = {
p1 != nullptr ? static_cast<const Printy&>(CreatePrinty())
// CHECK: cond.true:
// CHECK-NEXT: call void @_Z12CreatePrintyv
// CHECK-NEXT: store i1 true, ptr [[BRANCH1_DEFERRED]], align 1
// CHECK-NEXT: store i1 true, ptr [[BRANCH1_LIFEEXT]], align 1
// CHECK-NEXT: br label %{{.*}}
: foo() ? static_cast<const Printy&>(CreatePrinty(CreateTemp()))
: *p1,
({
if (foo()) return;
Printy("c");
// CHECK: if.end:
// CHECK-NEXT: call void @_ZN6PrintyC1EPKc
// CHECK-NEXT: store ptr
})};
// CHECK-NEXT: store i1 false, ptr [[BRANCH1_DEFERRED]], align 1
// CHECK-NEXT: store i32 0, ptr %cleanup.dest.slot, align 4
// CHECK-NEXT: br label %cleanup
}
void NewArrayInit() {
// CHECK-LABEL: define dso_local void @_Z12NewArrayInitv()
// CHECK: %array.init.end = alloca ptr, align 8
// CHECK: store ptr %0, ptr %array.init.end, align 8
Printy *array = new Printy[3]{
"a",
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK: store ptr %array.exp.next, ptr %array.init.end, align 8
"b",
// CHECK: call void @_ZN6PrintyC1EPKc
// CHECK: store ptr %array.exp.next1, ptr %array.init.end, align 8
({
if (foo()) {
return;
// CHECK: if.then:
// CHECK: br i1 %arraydestroy.isempty, label %arraydestroy.done{{.*}}, label %arraydestroy.body
}
"b";
// CHECK: if.end:
// CHECK: call void @_ZN6PrintyC1EPKc
})};
// CHECK: arraydestroy.body:
// CHECK-NEXT: %arraydestroy.elementPast = phi ptr [ %{{.*}}, %if.then ], [ %arraydestroy.element, %arraydestroy.body ]
// CHECK-NEXT: %arraydestroy.element = getelementptr inbounds %struct.Printy, ptr %arraydestroy.elementPast, i64 -1
// CHECK-NEXT: call void @_ZN6PrintyD1Ev(ptr noundef nonnull align 8 dereferenceable(8) %arraydestroy.element)
// CHECK-NEXT: %arraydestroy.done = icmp eq ptr %arraydestroy.element, %0
// CHECK-NEXT: br i1 %arraydestroy.done, label %arraydestroy.done{{.*}}, label %arraydestroy.body
// CHECK: arraydestroy.done{{.*}}: ; preds = %arraydestroy.body, %if.then
// CHECK-NEXT: br label %return
}
void DestroyInConditionalCleanup() {
// EH-LABEL: DestroyInConditionalCleanupv()
// NOEH-LABEL: DestroyInConditionalCleanupv()
struct A {
A() {}
~A() {}
};
struct Value {
Value(A) {}
~Value() {}
};
struct V2 {
Value K;
Value V;
};
// Verify we use conditional cleanups.
(void)(foo() ? V2{A(), A()} : V2{A(), A()});
// NOEH: cond.true:
// NOEH: call void @_ZZ27DestroyInConditionalCleanupvEN1AC1Ev
// NOEH: store ptr %{{.*}}, ptr %cond-cleanup.save
// EH: cond.true:
// EH: invoke void @_ZZ27DestroyInConditionalCleanupvEN1AC1Ev
// EH: store ptr %{{.*}}, ptr %cond-cleanup.save
}
void ArrayInitWithContinue() {
// CHECK-LABEL: @_Z21ArrayInitWithContinuev
// Verify that we start to emit the array destructor.
// CHECK: %arrayinit.endOfInit = alloca ptr, align 8
for (int i = 0; i < 1; ++i) {
Printy arr[2] = {"a", ({
if (foo()) {
continue;
}
"b";
})};
}
}
struct [[clang::trivial_abi]] HasTrivialABI {
HasTrivialABI();
~HasTrivialABI();
};
void AcceptTrivialABI(HasTrivialABI, int);
void TrivialABI() {
// CHECK-LABEL: define dso_local void @_Z10TrivialABIv()
AcceptTrivialABI(HasTrivialABI(), ({
if (foo()) return;
// CHECK: if.then:
// CHECK-NEXT: call void @_ZN13HasTrivialABID1Ev
// CHECK-NEXT: br label %return
0;
}));
}
namespace CleanupFlag {
struct A {
A() {}
~A() {}
};
struct B {
B(const A&) {}
B() {}
~B() {}
};
struct S {
A a;
B b;
};
int AcceptS(S s);
void Accept2(int x, int y);
void InactiveNormalCleanup() {
// CHECK-LABEL: define {{.*}}InactiveNormalCleanupEv()
// The first A{} below is an inactive normal cleanup which
// is not popped from EHStack on deactivation. This needs an
// "active" cleanup flag.
// CHECK: [[ACTIVE:%cleanup.isactive.*]] = alloca i1, align 1
// CHECK: call void [[A_CTOR:@.*AC1Ev]]
// CHECK: store i1 true, ptr [[ACTIVE]], align 1
// CHECK: call void [[A_CTOR]]
// CHECK: call void [[B_CTOR:@.*BC1ERKNS_1AE]]
// CHECK: store i1 false, ptr [[ACTIVE]], align 1
// CHECK: call noundef i32 [[ACCEPTS:@.*AcceptSENS_1SE]]
Accept2(AcceptS({.a = A{}, .b = A{}}), ({
if (foo()) return;
// CHECK: if.then:
// CHECK: br label %cleanup
0;
// CHECK: if.end:
// CHECK: call void [[ACCEPT2:@.*Accept2Eii]]
// CHECK: br label %cleanup
}));
// CHECK: cleanup:
// CHECK: call void [[S_DTOR:@.*SD1Ev]]
// CHECK: call void [[A_DTOR:@.*AD1Ev]]
// CHECK: %cleanup.is_active = load i1, ptr [[ACTIVE]]
// CHECK: br i1 %cleanup.is_active, label %cleanup.action, label %cleanup.done
// CHECK: cleanup.action:
// CHECK: call void [[A_DTOR]]
// The "active" cleanup flag is not required for unused cleanups.
Accept2(AcceptS({.a = A{}, .b = A{}}), 0);
// CHECK: cleanup.cont:
// CHECK: call void [[A_CTOR]]
// CHECK-NOT: store i1 true
// CHECK: call void [[A_CTOR]]
// CHECK: call void [[B_CTOR]]
// CHECK-NOT: store i1 false
// CHECK: call noundef i32 [[ACCEPTS]]
// CHECK: call void [[ACCEPT2]]
// CHECK: call void [[S_DTOR]]
// CHECK: call void [[A_DTOR]]
// CHECK: br label %return
}
} // namespace CleanupFlag