Rather than pushing inactive cleanups for the block captures at the entry of a full expression and activating them during the creation of the block literal, just call pushLifetimeExtendedDestroy to ensure the cleanups are popped at the end of the scope enclosing the block expression. rdar://problem/63996471 Differential Revision: https://reviews.llvm.org/D81624
312 lines
8.9 KiB
C++
312 lines
8.9 KiB
C++
// RUN: %clang_cc1 %s -fblocks -triple x86_64-apple-darwin -emit-llvm -o - | FileCheck %s
|
|
|
|
// CHECK: %[[STRUCT_BLOCK_DESCRIPTOR:.*]] = type { i64, i64 }
|
|
// CHECK: @[[BLOCK_DESCRIPTOR22:.*]] = internal constant { i64, i64, i8*, i8*, i8*, i8* } { i64 0, i64 36, i8* bitcast (void (i8*, i8*)* @__copy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE to i8*), i8* bitcast (void (i8*)* @__destroy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE to i8*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @{{.*}}, i32 0, i32 0), i8* null }, align 8
|
|
|
|
namespace test0 {
|
|
// CHECK-LABEL: define void @_ZN5test04testEi(
|
|
// CHECK: define internal void @___ZN5test04testEi_block_invoke{{.*}}(
|
|
// CHECK: define internal void @___ZN5test04testEi_block_invoke_2{{.*}}(
|
|
void test(int x) {
|
|
^{ ^{ (void) x; }; };
|
|
}
|
|
}
|
|
|
|
extern void (^out)();
|
|
|
|
namespace test1 {
|
|
// Capturing const objects doesn't require a local block.
|
|
// CHECK-LABEL: define void @_ZN5test15test1Ev()
|
|
// CHECK: store void ()* bitcast ({{.*}} @__block_literal_global{{.*}} to void ()*), void ()** @out
|
|
void test1() {
|
|
const int NumHorsemen = 4;
|
|
out = ^{ (void) NumHorsemen; };
|
|
}
|
|
|
|
// That applies to structs too...
|
|
// CHECK-LABEL: define void @_ZN5test15test2Ev()
|
|
// CHECK: store void ()* bitcast ({{.*}} @__block_literal_global{{.*}} to void ()*), void ()** @out
|
|
struct loc { double x, y; };
|
|
void test2() {
|
|
const loc target = { 5, 6 };
|
|
out = ^{ (void) target; };
|
|
}
|
|
|
|
// ...unless they have mutable fields...
|
|
// CHECK-LABEL: define void @_ZN5test15test3Ev()
|
|
// CHECK: [[BLOCK:%.*]] = alloca [[BLOCK_T:<{.*}>]],
|
|
// CHECK: [[T0:%.*]] = bitcast [[BLOCK_T]]* [[BLOCK]] to void ()*
|
|
// CHECK: store void ()* [[T0]], void ()** @out
|
|
struct mut { mutable int x; };
|
|
void test3() {
|
|
const mut obj = { 5 };
|
|
out = ^{ (void) obj; };
|
|
}
|
|
|
|
// ...or non-trivial destructors...
|
|
// CHECK-LABEL: define void @_ZN5test15test4Ev()
|
|
// CHECK: [[OBJ:%.*]] = alloca
|
|
// CHECK: [[BLOCK:%.*]] = alloca [[BLOCK_T:<{.*}>]],
|
|
// CHECK: [[T0:%.*]] = bitcast [[BLOCK_T]]* [[BLOCK]] to void ()*
|
|
// CHECK: store void ()* [[T0]], void ()** @out
|
|
struct scope { int x; ~scope(); };
|
|
void test4() {
|
|
const scope obj = { 5 };
|
|
out = ^{ (void) obj; };
|
|
}
|
|
|
|
// ...or non-trivial copy constructors, but it's not clear how to do
|
|
// that and still have a constant initializer in '03.
|
|
}
|
|
|
|
namespace test2 {
|
|
struct A {
|
|
A();
|
|
A(const A &);
|
|
~A();
|
|
};
|
|
|
|
struct B {
|
|
B();
|
|
B(const B &);
|
|
~B();
|
|
};
|
|
|
|
// CHECK-LABEL: define void @_ZN5test24testEv()
|
|
void test() {
|
|
__block A a;
|
|
__block B b;
|
|
^{ (void)a; (void)b; };
|
|
}
|
|
|
|
// CHECK-LABEL: define internal void @__Block_byref_object_copy
|
|
// CHECK: call void @_ZN5test21AC1ERKS0_(
|
|
|
|
// CHECK-LABEL: define internal void @__Block_byref_object_dispose
|
|
// CHECK: call void @_ZN5test21AD1Ev(
|
|
|
|
// CHECK-LABEL: define internal void @__Block_byref_object_copy
|
|
// CHECK: call void @_ZN5test21BC1ERKS0_(
|
|
|
|
// CHECK-LABEL: define internal void @__Block_byref_object_dispose
|
|
// CHECK: call void @_ZN5test21BD1Ev(
|
|
}
|
|
|
|
// rdar://problem/9334739
|
|
// Make sure we mark destructors for parameters captured in blocks.
|
|
namespace test3 {
|
|
struct A {
|
|
A(const A&);
|
|
~A();
|
|
};
|
|
|
|
struct B : A {
|
|
};
|
|
|
|
void test(B b) {
|
|
extern void consume(void(^)());
|
|
consume(^{ (void) b; });
|
|
}
|
|
}
|
|
|
|
// rdar://problem/9971485
|
|
namespace test4 {
|
|
struct A {
|
|
A();
|
|
~A();
|
|
};
|
|
|
|
void foo(A a);
|
|
|
|
void test() {
|
|
extern void consume(void(^)());
|
|
consume(^{ return foo(A()); });
|
|
}
|
|
// CHECK-LABEL: define void @_ZN5test44testEv()
|
|
// CHECK-LABEL: define internal void @___ZN5test44testEv_block_invoke
|
|
// CHECK: [[TMP:%.*]] = alloca [[A:%.*]], align 1
|
|
// CHECK-NEXT: store i8* [[BLOCKDESC:%.*]], i8** {{.*}}, align 8
|
|
// CHECK-NEXT: bitcast i8* [[BLOCKDESC]] to <{ i8*, i32, i32, i8*, %[[STRUCT_BLOCK_DESCRIPTOR]]* }>*
|
|
// CHECK: call void @_ZN5test41AC1Ev([[A]]* [[TMP]])
|
|
// CHECK-NEXT: call void @_ZN5test43fooENS_1AE([[A]]* [[TMP]])
|
|
// CHECK-NEXT: call void @_ZN5test41AD1Ev([[A]]* [[TMP]])
|
|
// CHECK-NEXT: ret void
|
|
}
|
|
|
|
namespace test5 {
|
|
struct A {
|
|
unsigned afield;
|
|
A();
|
|
A(const A&);
|
|
~A();
|
|
void foo() const;
|
|
};
|
|
|
|
void doWithBlock(void(^)());
|
|
|
|
void test(bool cond) {
|
|
A x;
|
|
void (^b)() = (cond ? ^{ x.foo(); } : (void(^)()) 0);
|
|
doWithBlock(b);
|
|
}
|
|
|
|
// CHECK-LABEL: define void @_ZN5test54testEb(
|
|
// CHECK: [[COND:%.*]] = alloca i8
|
|
// CHECK-NEXT: [[X:%.*]] = alloca [[A:%.*]], align 4
|
|
// CHECK-NEXT: [[B:%.*]] = alloca void ()*, align 8
|
|
// CHECK-NEXT: [[BLOCK:%.*]] = alloca [[BLOCK_T:.*]], align 8
|
|
// CHECK-NEXT: [[CLEANUP_ACTIVE:%.*]] = alloca i1
|
|
// CHECK-NEXT: [[COND_CLEANUP_SAVE:%.*]] = alloca [[A]]*, align 8
|
|
// CHECK-NEXT: [[T0:%.*]] = zext i1
|
|
// CHECK-NEXT: store i8 [[T0]], i8* [[COND]], align 1
|
|
// CHECK-NEXT: call void @_ZN5test51AC1Ev([[A]]* [[X]])
|
|
// CHECK-NEXT: [[T0:%.*]] = load i8, i8* [[COND]], align 1
|
|
// CHECK-NEXT: [[T1:%.*]] = trunc i8 [[T0]] to i1
|
|
// CHECK-NEXT: store i1 false, i1* [[CLEANUP_ACTIVE]]
|
|
// CHECK-NEXT: br i1 [[T1]],
|
|
|
|
// CHECK-NOT: br
|
|
// CHECK: [[CAPTURE:%.*]] = getelementptr inbounds [[BLOCK_T]], [[BLOCK_T]]* [[BLOCK]], i32 0, i32 5
|
|
// CHECK-NEXT: call void @_ZN5test51AC1ERKS0_([[A]]* [[CAPTURE]], [[A]]* nonnull align {{[0-9]+}} dereferenceable({{[0-9]+}}) [[X]])
|
|
// CHECK-NEXT: store i1 true, i1* [[CLEANUP_ACTIVE]]
|
|
// CHECK-NEXT: store [[A]]* [[CAPTURE]], [[A]]** [[COND_CLEANUP_SAVE]], align 8
|
|
// CHECK-NEXT: bitcast [[BLOCK_T]]* [[BLOCK]] to void ()*
|
|
// CHECK-NEXT: br label
|
|
// CHECK: br label
|
|
// CHECK: phi
|
|
// CHECK-NEXT: store
|
|
// CHECK-NEXT: load
|
|
// CHECK-NEXT: call void @_ZN5test511doWithBlockEU13block_pointerFvvE(
|
|
// CHECK-NEXT: [[T0:%.*]] = load i1, i1* [[CLEANUP_ACTIVE]]
|
|
// CHECK-NEXT: br i1 [[T0]]
|
|
// CHECK: [[T3:%.*]] = load [[A]]*, [[A]]** [[COND_CLEANUP_SAVE]], align 8
|
|
// CHECK-NEXT: call void @_ZN5test51AD1Ev([[A]]* [[T3]])
|
|
// CHECK-NEXT: br label
|
|
// CHECK: call void @_ZN5test51AD1Ev([[A]]* [[X]])
|
|
// CHECK-NEXT: ret void
|
|
}
|
|
|
|
namespace test6 {
|
|
struct A {
|
|
A();
|
|
~A();
|
|
};
|
|
|
|
void foo(const A &, void (^)());
|
|
void bar();
|
|
|
|
void test() {
|
|
// Make sure that the temporary cleanup isn't somehow captured
|
|
// within the block.
|
|
foo(A(), ^{ bar(); });
|
|
bar();
|
|
}
|
|
|
|
// CHECK-LABEL: define void @_ZN5test64testEv()
|
|
// CHECK: [[TEMP:%.*]] = alloca [[A:%.*]], align 1
|
|
// CHECK-NEXT: call void @_ZN5test61AC1Ev([[A]]* [[TEMP]])
|
|
// CHECK-NEXT: call void @_ZN5test63fooERKNS_1AEU13block_pointerFvvE(
|
|
// CHECK-NEXT: call void @_ZN5test61AD1Ev([[A]]* [[TEMP]])
|
|
// CHECK-NEXT: call void @_ZN5test63barEv()
|
|
// CHECK-NEXT: ret void
|
|
}
|
|
|
|
namespace test7 {
|
|
int f() {
|
|
static int n;
|
|
int *const p = &n;
|
|
return ^{ return *p; }();
|
|
}
|
|
}
|
|
|
|
namespace test8 {
|
|
// <rdar://problem/10832617>: failure to capture this after skipping rebuild
|
|
// of the 'this' pointer.
|
|
struct X {
|
|
int x;
|
|
|
|
template<typename T>
|
|
int foo() {
|
|
return ^ { return x; }();
|
|
}
|
|
};
|
|
|
|
template int X::foo<int>();
|
|
}
|
|
|
|
// rdar://13459289
|
|
namespace test9 {
|
|
struct B {
|
|
void *p;
|
|
B();
|
|
B(const B&);
|
|
~B();
|
|
};
|
|
|
|
void use_block(void (^)());
|
|
void use_block_2(void (^)(), const B &a);
|
|
|
|
// Ensuring that creating a non-trivial capture copy expression
|
|
// doesn't end up stealing the block registration for the block we
|
|
// just parsed. That block must have captures or else it won't
|
|
// force registration. Must occur within a block for some reason.
|
|
void test() {
|
|
B x;
|
|
use_block(^{
|
|
int y;
|
|
use_block_2(^{ (void)y; }, x);
|
|
});
|
|
}
|
|
}
|
|
|
|
namespace test10 {
|
|
// Check that 'v' is included in the copy helper function name to indicate
|
|
// the constructor taking a volatile parameter is called to copy the captured
|
|
// object.
|
|
|
|
// CHECK-LABEL: define linkonce_odr hidden void @__copy_helper_block_8_32c16_ZTSVN6test101BE(
|
|
// CHECK: call void @_ZN6test101BC1ERVKS0_(
|
|
// CHECK-LABEL: define linkonce_odr hidden void @__destroy_helper_block_8_32c16_ZTSVN6test101BE(
|
|
// CHECK: call void @_ZN6test101BD1Ev(
|
|
|
|
struct B {
|
|
int a;
|
|
B();
|
|
B(const B &);
|
|
B(const volatile B &);
|
|
~B();
|
|
};
|
|
|
|
void test() {
|
|
volatile B x;
|
|
^{ (void)x; };
|
|
}
|
|
}
|
|
|
|
// Copy/dispose helper functions and block descriptors of blocks that capture
|
|
// objects that are non-external and non-trivial have internal linkage.
|
|
|
|
// CHECK-LABEL: define internal void @_ZN12_GLOBAL__N_14testEv(
|
|
// CHECK: store %[[STRUCT_BLOCK_DESCRIPTOR]]* bitcast ({ i64, i64, i8*, i8*, i8*, i8* }* @[[BLOCK_DESCRIPTOR22]] to %[[STRUCT_BLOCK_DESCRIPTOR]]*), %[[STRUCT_BLOCK_DESCRIPTOR]]** %{{.*}}, align 8
|
|
|
|
// CHECK-LABEL: define internal void @__copy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE(
|
|
// CHECK-LABEL: define internal void @__destroy_helper_block_8_32c22_ZTSN12_GLOBAL__N_11BE(
|
|
|
|
namespace {
|
|
struct B {
|
|
int a;
|
|
B();
|
|
B(const B &);
|
|
~B();
|
|
};
|
|
|
|
void test() {
|
|
B x;
|
|
^{ (void)x; };
|
|
}
|
|
}
|
|
|
|
void callTest() {
|
|
test();
|
|
}
|